1
\$\begingroup\$

I am having a problem with rendering a quad in opengl via 4 lines (5 vertices via GL_LINE_STRIP). The rendering is generally working fine in terms of connecting the lines into a quad. The problem is, that the lines in the bottom left corner are not connected. Following screenshot shows what I am talking about. Left is a screenshot from the entire scene, right a close up zoom in where one can see that the lines do not connect correctly.

lines do not connect at bottom left

The coordinates for the corner vertices in NDC are (x,y)

top left     -0.5,  0.5
bottom left  -0.5, -0.5
bottom right  0.5, -0.5
top right     0.5, 0.5

I really tried to wrap my head around where this is coming from but I couldn't figure it out and I also didn't find any helpful information on the internet. It seems to me, that the line from top left to bottom left is just one pixel short and does not connect to the bottom line.

I included a working minimal example in c++. Maybe someone can help me find out where this is coming from. I am using Visual Studio 2017, OpenGL 460 core, glfw3 and glad 0.1.29.

Main program:

//cpp
#include <iostream>
#include <vector>
#include <cstdio>

//opengl, etc.
#include <glad/glad.h>
#include <glfw/glfw3.h>

//own
#include <shader.h>

//forward declarations
GLFWwindow* initOpenGL(const int pScreenWidth, const int pScreenHeight);
void framebufferSizeCallback(GLFWwindow *window, int width, int height);

int main(int argc, char *args[])
{
  const int SCREEN_WIDTH = 800;
  const int SCREEN_HEIGHT = 800;

  GLFWwindow *window = initOpenGL(SCREEN_WIDTH, SCREEN_HEIGHT);
  Shader shader("shader_code/vertex_shader_quad.glsl", "shader_code/fragment_shader_quad.glsl");
  shader.use();
  glfwSwapInterval(1);

  std::vector<float> quadVertices;
  quadVertices.push_back(-0.5f);
  quadVertices.push_back(0.5f);

  quadVertices.push_back(-0.5f);
  quadVertices.push_back(-0.5f);

  quadVertices.push_back(0.5f);
  quadVertices.push_back(-0.5f);

  quadVertices.push_back(0.5f);
  quadVertices.push_back(0.5f);

  quadVertices.push_back(-0.5f);
  quadVertices.push_back(0.5f);

  glClearColor(0.2f, 0.3f, 0.3f, 1.0f);
  glClear(GL_COLOR_BUFFER_BIT);

  unsigned int vbo, vao;

  glGenVertexArrays(1, &vao);
  glGenBuffers(1, &vbo);
  glBindVertexArray(vao);

  glBindBuffer(GL_ARRAY_BUFFER, vbo);
  glBufferData(GL_ARRAY_BUFFER, quadVertices.size() * sizeof(float), &quadVertices.front(), GL_STATIC_DRAW);
  glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 2 * sizeof(float), (void*)0);
  glEnableVertexAttribArray(0);

  glDrawArrays(GL_LINE_STRIP, 0, (quadVertices.size() / 2));

  glDeleteVertexArrays(1, &vao);
  glDeleteBuffers(1, &vbo);

  glfwSwapBuffers(window);
  glfwPollEvents();

  //let program run until button is pressed
  std::getchar();
}

GLFWwindow* initOpenGL(const int pScreenWidth, const int pScreenHeight)
{
  glfwInit();
  glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
  glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
  glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);

  GLFWwindow *window = glfwCreateWindow(pScreenWidth, pScreenHeight, "OpenGLRenderer", NULL, NULL);
  if (window == NULL)
  {
    std::cout << "Failed to create glfw window" << std::endl;
    glfwTerminate();
    return NULL;
  }
  glfwMakeContextCurrent(window);
  glfwSetFramebufferSizeCallback(window, framebufferSizeCallback);

  if (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress))
  {
    std::cout << "Failed to initialize GLAD" << std::endl;
    return NULL;
  }

  return window;
}

void framebufferSizeCallback(GLFWwindow *window, int width, int height)
{
  glViewport(0, 0, width, height);
}

Shaders:

vertex shader:

#version 460 core

layout (location=0) in vec2 inPos;
out vec3 vertexColor;

void main()
{
  gl_Position = vec4(inPos, 0.0f, 1.0f);
  vertexColor = vec3(0.91f, 0.84f, 0.42f);
}

fragment shader

#version 460 core
in vec3 vertexColor;
out vec4 color;
void main()
{
  color = vec4(vertexColor, 1.0f);
}

Custom Shader Class:

#include <shader.h>


Shader::Shader(const char *pVertexPath, const char *pFragmentPath)
{
  std::string vertexCodeInput, fragmentCodeInput;
  std::ifstream vertexFile, fragmentFile;
  vertexFile.exceptions(std::ifstream::failbit | std::ifstream::badbit);
  fragmentFile.exceptions(std::ifstream::failbit | std::ifstream::badbit);
  try
  {
    vertexFile.open(pVertexPath);
    fragmentFile.open(pFragmentPath);
    std::stringstream vertexFileStream, fragmentFileStream;
    vertexFileStream << vertexFile.rdbuf();
    fragmentFileStream << fragmentFile.rdbuf();
    vertexFile.close();
    fragmentFile.close();

    vertexCodeInput = vertexFileStream.str();
    fragmentCodeInput = fragmentFileStream.str();
  }
  catch(std::fstream::failure eX)
  {
    std::cout << "ERROR::SHADER::FILE_NOT_SUCCESFULLY_READ" << std::endl;
  }

  const char* vShaderSource = vertexCodeInput.c_str();
  const char* fShaderSource = fragmentCodeInput.c_str();

  unsigned int vertexShader, fragmentShader;

  vertexShader = glCreateShader(GL_VERTEX_SHADER);
  glShaderSource(vertexShader, 1, &vShaderSource, NULL);
  glCompileShader(vertexShader);

  //check compilation result
  int success;
  glGetShaderiv(vertexShader, GL_COMPILE_STATUS, &success);

  if (!success)
  {
    char vShaderInfoLog[1024];
    glGetShaderInfoLog(vertexShader, 1024, NULL, vShaderInfoLog);
    std::cout << "Error::SHADER::VERTEX::COMPILATION_FAILED" << std::endl << vShaderInfoLog << std::endl;
  }
  success = 0;

  //FRAGMENT SHADER
  //create shader, load shader code, compile shader
  fragmentShader = glCreateShader(GL_FRAGMENT_SHADER);
  glShaderSource(fragmentShader, 1, &fShaderSource, NULL);
  glCompileShader(fragmentShader);

  glGetShaderiv(fragmentShader, GL_COMPILE_STATUS, &success);
  if (!success)
  {
    char fShaderInfoLog[1024];
    glGetShaderInfoLog(fragmentShader, 1024, NULL, fShaderInfoLog);
    std::cout << "Error::SHADER::FRAGMENT::COMPILATION_FAILED" << std::endl << fShaderInfoLog << std::endl;
  }
  success = 0;  

  //create shader program, attach shaders
  id = glCreateProgram();
  glAttachShader(id, vertexShader);
  glAttachShader(id, fragmentShader);
  glLinkProgram(id);

  glGetProgramiv(id, GL_LINK_STATUS, &success);
  if (!success)
  {
    char shaderProgramInfoLog[1024];
    glGetProgramInfoLog(id, 1024, NULL, shaderProgramInfoLog);
    std::cout << "Error::SHADER::PROGRAM::COMPILATION_FAILED" << std::endl << shaderProgramInfoLog << std::endl;
  }
  success = 0;

  //already linked shaders can be deleted
  glDeleteShader(vertexShader);
  glDeleteShader(fragmentShader);
}

void Shader::use()
{
  glUseProgram(id);
}

void Shader::setBoolUniform(const std::string pUniformName, const bool pValue) const
{
  glUniform1i(glGetUniformLocation(id, pUniformName.c_str()), (int)pValue);
}

void Shader::setFloatUniform(const std::string pUniformName, const float pValue) const
{
  glUniform1f(glGetUniformLocation(id, pUniformName.c_str()), pValue);
}

void Shader::setIntUniform(const std::string pUniformName, const unsigned int pValue) const
{
  unsigned int uniformLocation = glGetUniformLocation(id, pUniformName.c_str());
  glUniform1i(glGetUniformLocation(id, pUniformName.c_str()), pValue);
}

Thanks in advance!

\$\endgroup\$
  • \$\begingroup\$ Have you tried starting and ending your line in the middle of an edge, rather than at a corner? 1 extra vertex, but it ensures all cornets are handled equally. \$\endgroup\$ – DMGregory Dec 8 '19 at 15:23
  • \$\begingroup\$ I will try if this helps getting rid of it. Thank you \$\endgroup\$ – Tremah Dec 8 '19 at 15:55
1
\$\begingroup\$

You're out of luck. There is nothing wrong in your code, and there's nothing you can do in your code to guarantee any set of arbitrary lines will always meet perfectly by using the technique you are using.

For line rasterization, OpenGL uses the diamond exit line rasterization rule (there is a primer of rasterization rules on the Direct3D documentation), but it allows some deviation to be performed by the specific OpenGL driver you are using.

Per the OpenGL FAQ chapter 14 on rasterization:

14.090 How do I obtain exact pixelization of lines?

The OpenGL specification allows for a wide range of line rendering hardware, so exact pixelization may not be possible at all.

[...]

14.100 How do I turn on wide-line endpoint capping or mitering?

[...]

OpenGL doesn't provide a mechanism to cleanly join lines that share common vertices nor to cleanly cap the endpoints.

[...]

14.120 If I draw a quad in fill mode and again in line mode, why don't the lines hit the same pixels as the filled quad?

Filled primitives and line primitives follow different rules for rasterization.

[...]

(The OpenGL specification allows for some deviation from the diamond exit line rasterization rule, but it makes no difference in this scenario.)

I don't know about your specific use case, but the general idea is that this shouldn't matter. You should ask yourself if having pixel perfect joins between the lines is necessary for your application, and if it is, I suggest you write your own rasterizer that you can completely control.

| improve this answer | |
\$\endgroup\$
  • \$\begingroup\$ thanks for your reply. I figured that it has something to do with rasterization and maybe also my hardware. I'll just go with it for now and see if it becomes a major issue. \$\endgroup\$ – Tremah Dec 8 '19 at 15:54
0
\$\begingroup\$

If you only need to render closed loops of lines, consider using GL_LINE_LOOP instead of Gl_LINE_STRIP. It should link all the vertices properly.

For example, glDrawArrays(GL_LINE_STRIP, 0, (quadVertices.size() / 2)); Becomes glDrawArrays(GL_LINE_LOOP, 0, (quadVertices.size() / 2));

You also will need to remove the last vertex from quadVertives, as it's redundant.

| improve this answer | |
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.