Search code examples
pythonopengltriangulation

Which OpenGL Primitive should I use for draw an triangulation?


I've the following image :

enter image description here

That I was able to triangulate like the following : enter image description here

I did it using the triangle library of python. My triangulation result is stored into a dict object which look like that:

>>> triangulation["vertices"]
array([[ 23.        , 282.        ],
       [ 24.        , 254.        ],
       [ 30.        , 239.        ],
       [ 43.        , 219.        ],
       [ 60.        , 204.        ], ...  And so on ...



>>> triangulation["triangles"]
array([[ 89, 106, 105],
       [ 99,  35,  86],
       [110,  68,  87],
       [ 47,  66,  83],
       [ 72,  82,  74], ...  And so on ...

Now, I want to distord and draw this texture as a mesh using OpenGL. I was wondering which primitive should I use? I think that TRIANGLE_STRIP is the right solution but it's a complex triangulation and it seems obvious that only one TRIANGLE_STRIP will not be enough.


Solution

  • You've 2 arrays. The first array contains the 2 dimensional vertex coordinates and the second array contains the indices.
    You have to use glDrawElements tor draw the triangle primitives which are contained in the 2nd array. The indices refer to the corresponding vertex coordinates of the 1st array.

    First you've to create a floating point buffer for the vertex coordinates and an integral buffer for the indices. The easiest way is to use NumPy, to transform nested list or arrays to array buffers.

    Assuming that you've an array of vertices in the form [[x0, y0], [x1, y1], [x2, y2],...] and an array of indices in the form [[a0, b0, c0], [a1, b1, c1], [a2, b2, c2],...], then the buffers can be created like this:

    import numpy as np
    
    vertex_array = np.array(vertices, dtype=np.float32)
    
    no_of_indices = len(indices) * 3
    index_array = np.array(indices, dtype=np.uint32)
    

    The same can be done by using ctypes, instead of NumPy. But then the lists have to be flattened in the form [x0, y0, x1, y1, x2, y2,...] respectively [a0, b0, c0, a1, b1, c1, a2, b2, c2,...]:

    vertex_array = (ctypes.c_float * len(flat_vertices))(*flat_vertices)
    
    no_of_indices = len(flat_indices) * 3
    index_array = (ctypes.c_uint32 * no_of_indices)(*flat_indices)
    

    Create a vertex array object. See Vertex Specification:

    vao = glGenVertexArrays(1)
    glBindVertexArray(vao)
    
    ibo = glGenBuffers(1)
    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ibo)
    glBufferData(GL_ELEMENT_ARRAY_BUFFER, index_array, GL_STATIC_DRAW)
    
    vbo = glGenBuffers(1)
    glBindBuffer(GL_ARRAY_BUFFER, vbo)
    glBufferData(GL_ARRAY_BUFFER, vertex_array, GL_STATIC_DRAW)
    
    glVertexAttribPointer(0, 2, GL_FLOAT, False, 0, None)
    glEnableVertexAttribArray(0)
    
    glBindBuffer(GL_ARRAY_BUFFER, 0)
    glBindVertexArray(0)
    

    If you want to draw the triangle primitives it is sufficient to bind the vertex array object and to call glDrawElements:

    glBindVertexArray(vao)
    glDrawElements(GL_TRIANGLES, no_of_indices, GL_UNSIGNED_INT, None)
    

    Regarding to the comment:

    [...] I already know how to texture a plane by using glTexCoord2f then glVertex3f but didn't figure out how I can do it with a vao.

    The easiest way is to create a separate array of texture coordinates. Assuming you've the texture coordinates texAttr in the form [[u0, v0], [u1, v1], [u2, v2],...]. Generating the buffer is straight forward:

    texAttr_array = np.array(texAttr, dtype=np.float32)
    

    If you have a shader program, then you've to add the texture coordinate attribute:

    e.g.

    layout (location = 0) in vec2 vertex;
    layout (location = 1) in vec2 texAttr;
    

    and to define an array of generic vertex attribute data.

    vao = glGenVertexArrays(1)
    glBindVertexArray(vao)
    
    # [...] index buffer
    
    vbo = glGenBuffers(2)
    glBindBuffer(GL_ARRAY_BUFFER, vbo[0])
    glBufferData(GL_ARRAY_BUFFER, vertex_array, GL_STATIC_DRAW)
    glBindBuffer(GL_ARRAY_BUFFER, vbo[1])
    glBufferData(GL_ARRAY_BUFFER, texAttr_array, GL_STATIC_DRAW)
    
    glVertexAttribPointer(0, 2, GL_FLOAT, False, 0, None)
    glEnableVertexAttribArray(0)
    glVertexAttribPointer(1, 2, GL_FLOAT, False, 0, None)
    glEnableVertexAttribArray(1)
    
    glBindBuffer(GL_ARRAY_BUFFER, 0)
    glBindVertexArray(0)
    

    If you use compatibility profile and the fixed function attributes, then you've to specify the glTexCoordPointer and enable the client state GL_TEXTURE_COORD_ARRAY.
    Note, the client state GL_VERTEX_ARRAY and glVertexPointer is mapped to the vertex attribute 0. See What are the Attribute locations for fixed function pipeline in OpenGL 4.0++ core profile?:

    vao = glGenVertexArrays(1)
    glBindVertexArray(vao)
    
    # [...] index buffer
    
    vbo = glGenBuffers(2)
    glBindBuffer(GL_ARRAY_BUFFER, vbo[0])
    glBufferData(GL_ARRAY_BUFFER, vertex_array, GL_STATIC_DRAW)
    glBindBuffer(GL_ARRAY_BUFFER, vbo[1])
    glBufferData(GL_ARRAY_BUFFER, texAttr_array, GL_STATIC_DRAW)
    
    glVertexPointer(2, GL_FLOAT, 0, None)
    glEnableClientState(GL_VERTEX_ARRAY)
    glTexCoordPointer(2, GL_FLOAT, 0, None)
    glEnableClientState(GL_TEXTURE_COORD_ARRAY)
    
    glBindBuffer(GL_ARRAY_BUFFER, 0)
    glBindVertexArray(0)
    

    Note, if you don't use a shader, then 2 dimensional texturing has to be enabled by

    glEnable(GL_TEXTURE_2D)
    

    Regarding to the comment:

    I d'like to displace vertex one by one [...]

    A single vertex coordinate can be changed by glBufferSubData, where the 2nd parameter is a byte offset to the vertex coordinate. The offset for the coordinate i would be 4*2*i (4 is the size of float in bytes, and each coordinate consist of 2 components x and y).