Artifacts rendering high-poly procedural meshes with OpenGL

277 views Asked by At

I am having some issues when rendering some procedural generated meshes. There are some really wierd artifacts when rendering high-poly count meshes (not that much actualy). I have been able to isolate the problem, but I have no idea why this is happening. When I generate a low-poly mesh, the problem does not occur.

I created a small demo, so you can download the source. (See below)

These artifacts start appearing, in my application, when the mesh is of about ~90 triangles (120 vertices and 270 indices). To see it appearing in the demo, set initMesh to 150 rings ans 150 sectors.

Its been like a week I am working on this and I just cant reason why this is happening. What can it be?

Here is my mesh class:

#include "Mesh.h"

Mesh::Mesh()
{
    _vao = 0;

    _verticesVbo = 0;
    _texCoordsVbo = 0;
    _normalsVbo = 0;
    _indicesVbo = 0;
}

Mesh::~Mesh()
{   
    glDeleteBuffers(1, &_verticesVbo);
    glDeleteBuffers(1, &_texCoordsVbo);
    glDeleteBuffers(1, &_normalsVbo);
    glDeleteBuffers(1, &_indicesVbo);
    glDeleteVertexArrays(1, &_vao);
}

Mesh* Mesh::New(vector<Vertex> &vertices, vector<GLuint> &indices)
{
    Mesh* mesh = new Mesh();
    mesh->AddVertices(vertices, indices);
    return mesh;
}

void Mesh::AddVertices(vector<Vertex> &vertices, vector<GLuint> &indices)
{
    _vertices = vertices;
    _indices = indices;

    GLuint verticesSize = vertices.size() * 3 * sizeof(GLfloat);
    GLuint texCoordsSize = vertices.size() * 2 * sizeof(GLfloat);
    GLuint normalsSize = vertices.size() * 3 * sizeof(GLfloat);
    _indicesSize = indices.size() * sizeof(GLuint);

    GLfloat* vertexBuffer = new GLfloat[vertices.size() * 3];
    GLfloat* texCoordBuffer = new GLfloat[vertices.size() * 2];
    GLfloat* normalBuffer = new GLfloat[vertices.size() * 3];

    CreateBuffers(vertices, vertexBuffer, texCoordBuffer, normalBuffer);

    glGenVertexArrays(1, &_vao);
    glBindVertexArray(_vao);

    glGenBuffers(1, &_verticesVbo);
    glBindBuffer(GL_ARRAY_BUFFER, _verticesVbo);
    glBufferData(GL_ARRAY_BUFFER, verticesSize, vertexBuffer, GL_STATIC_DRAW);

    glGenBuffers(1, &_texCoordsVbo);
    glBindBuffer(GL_ARRAY_BUFFER, _texCoordsVbo);
    glBufferData(GL_ARRAY_BUFFER, texCoordsSize, texCoordBuffer, GL_STATIC_DRAW);

    glGenBuffers(1, &_normalsVbo);
    glBindBuffer(GL_ARRAY_BUFFER, _normalsVbo);
    glBufferData(GL_ARRAY_BUFFER, normalsSize, normalBuffer, GL_STATIC_DRAW);

    glGenBuffers(1, &_indicesVbo);
    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, _indicesVbo);
    glBufferData(GL_ELEMENT_ARRAY_BUFFER, _indicesSize, &indices[0], GL_STATIC_DRAW);

    delete[] vertexBuffer;
    delete[] texCoordBuffer;
    delete[] normalBuffer;
}

void Mesh::CreateBuffers(vector<Vertex> &vertices, 
                         GLfloat* &vertexBuffer, 
                         GLfloat* &texCoordBuffer, 
                         GLfloat* &normalBuffer)
{
    vector<Vertex>::iterator i;  
    unsigned int vIndex = 0;
    unsigned int tIndex = 0;
    unsigned int nIndex = 0;

    for (i = vertices.begin(); i != vertices.end(); ++i)
    {
        Vertex vertex = *i;

        GLfloat x = vertex.GetPosition().x;
        GLfloat y = vertex.GetPosition().y;
        GLfloat z = vertex.GetPosition().z;

        GLfloat u = vertex.GetTexCoord().x;
        GLfloat v = vertex.GetTexCoord().y;

        GLfloat r0 = vertex.GetNormal().x;
        GLfloat s0 = vertex.GetNormal().y;
        GLfloat t0 = vertex.GetNormal().z;

        vertexBuffer[vIndex++] = x;
        vertexBuffer[vIndex++] = y;
        vertexBuffer[vIndex++] = z;

        texCoordBuffer[tIndex++] = u;
        texCoordBuffer[tIndex++] = v;

        normalBuffer[nIndex++] = r0;
        normalBuffer[nIndex++] = s0;
        normalBuffer[nIndex++] = t0;
    }
}

void Mesh::Render()
{   
    glEnableVertexAttribArray(0);
    glBindBuffer(GL_ARRAY_BUFFER, _verticesVbo);
    glVertexAttribPointer((GLuint)0, 3, GL_FLOAT, GL_FALSE, 0, 0);

    glEnableVertexAttribArray(1);
    glBindBuffer(GL_ARRAY_BUFFER, _texCoordsVbo);
    glVertexAttribPointer((GLuint)1, 2, GL_FLOAT, GL_FALSE, 0, 0);

    glEnableVertexAttribArray(2);
    glBindBuffer(GL_ARRAY_BUFFER, _normalsVbo);
    glVertexAttribPointer((GLuint)2, 3, GL_FLOAT, GL_FALSE, 0, 0);

    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, _indicesVbo);
    glDrawElements(GL_TRIANGLES, _indicesSize, GL_UNSIGNED_INT, 0);

    glDisableVertexAttribArray(0);
    glDisableVertexAttribArray(1);
    glDisableVertexAttribArray(2);
}

Here the code to create the mesh:

void initMesh(float radius, int rings, int sectors)
{
    float piOver2 = M_PI * 0.5f;

    vector<Vertex> vertices;
    vector<unsigned int> indices;

    float const R = 1.0f/(float)(rings);
    float const S = 1.0f/(float)(sectors);
    unsigned int r, s;

    for(r = 0; r < rings + 1; r++) 
    {
        for(s = 0; s < sectors + 1; s++) 
        {
            float y = sin(piOver2 * r * R);
            float x = cos(2.0 * M_PI * s * S) * sin(piOver2 + piOver2 * r * R);
            float z = sin(2.0 * M_PI * s * S) * sin(piOver2 + piOver2 * r * R);

            vec3 position = vec3(x, y, z) * radius;
            vec3 normal = normalize(vec3(x, y, z)) * radius;
            vec2 texCoord = vec2(s * R, r * R) * radius;

            vertices.push_back(Vertex(position, texCoord, normal));
        }
    }

    for(r = 0; r < rings; r++) 
    {
        for(s = 0; s < sectors; s++) 
        {
            int a = r * (sectors + 1) + s;
            int b = (r + 1) * (sectors + 1) + s;
            int c = (r + 1) * (sectors + 1) + (s + 1);
            int d = r * (sectors + 1) + (s + 1);

            indices.push_back(a);
            indices.push_back(b);
            indices.push_back(c);

            indices.push_back(c);
            indices.push_back(d);
            indices.push_back(a);
        }
    }

    _mesh = Mesh::New(vertices, indices);
}

Here is the code to initialize OpenGL:

bool createGLWindow()
{
    _window = SDL_CreateWindow(
        "TestMesh",
        SDL_WINDOWPOS_CENTERED, 
        SDL_WINDOWPOS_CENTERED, 
        1024, 
        768, 
        SDL_WINDOW_SHOWN | SDL_WINDOW_OPENGL);

    if(_window == NULL)
    {
        LOG("Window could not be created! SDL_Error: " << SDL_GetError());
        return false;
    }

    SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE);
    SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 3);
    SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 3);

    SDL_GL_SetAttribute(SDL_GL_RED_SIZE, 8);
    SDL_GL_SetAttribute(SDL_GL_GREEN_SIZE, 8);
    SDL_GL_SetAttribute(SDL_GL_BLUE_SIZE, 8);
    SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, 16);
    SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1);

    _glContext = SDL_GL_CreateContext(_window);

    if (!_glContext)
    {
        LOG("Could not create context:" << SDL_GetError());
        return false;
    }

    glewExperimental = GL_TRUE;

    GLenum glewInitStatus = glewInit();

    if(glewInitStatus != GLEW_OK)
    {
        LOG("Error" << glewGetErrorString(glewInitStatus))
            return false;
    }

    return true;
}

Here the render function:

void render()
{
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

    _shader->Bind();
    _shader->GetUniform("mvp").Set(_projectionMatrix * _viewMatrix * _modelMatrix);
    _shader->GetUniform("color").Set(_color);
    _mesh->Render();
    _shader->Unbind();
}

And these are the vertex shader

#version 330

in vec3 inPosition;
in vec2 inTexCoord;
in vec3 inNormal;

uniform mat4 mvp;

out vec3 fragPosition;
out vec2 fragTexCoord;
out vec3 fragNormal;

void main()
{
    gl_Position = mvp * vec4(inPosition, 1.0);

    fragTexCoord = inTexCoord;
    fragPosition = inPosition;
    fragNormal = inNormal;
}

and fragment shader:

#version 330

uniform vec4 color;

in vec3 fragPosition;
in vec2 fragTexCoord;
in vec3 fragNormal;

out vec4 fragColor;

void main(void)
{
    vec3 lightPos = vec3(1.0, 1.0, 1.0);
    fragColor = max(dot(lightPos, fragNormal), 0.0) * 0.8 * color + color * 0.1;
}

The result when rendering a low-poly:

No artifacts

The result when rendering a high-poly:

enter image description here

And you can download the source of the demo here:

demo + source code

1

There are 1 answers

0
Reto Koradi On BEST ANSWER

There is an inconsistency in the units used for the index buffer size. In the AddVertices() method, this calculates the size in bytes:

_indicesSize = indices.size() * sizeof(GLuint);

It is then used correctly later in the same method as an argument to glBufferData(), which does need the size in bytes:

glBufferData(GL_ELEMENT_ARRAY_BUFFER, _indicesSize, &indices[0], GL_STATIC_DRAW);

But it also used in the Render() method as an argument to glDrawElements():

glDrawElements(GL_TRIANGLES, _indicesSize, GL_UNSIGNED_INT, 0);

In this case, the value passed in needs to be the number of indices, not the size in bytes. So the value of the argument is 4 times too large.

You will probably want to set the member variable to just the number of indices:

_indicesSize = indices.size();

but be careful that you still pass the size in bytes to gBufferData().

Another problem is that, at least in the part of the code that is posted, you never enable the depth test. You will need to do that somewhere during initialization:

glEnable(GL_DEPTH_TEST);