3

i need to visualize around 50k-60k points in openGL i managed to print them all but when i use rotate it takes so much time between each rotation because it just prints all the data every single frame. is there a way to print all the data once and freeze the importing of the data so it will keep the image but stop processing?

def PointClouds(pcd_files): #pcd_file
   glBegin(GL_POINTS)
   for i in pcd_files:
       pc = pypcd.PointCloud.from_path(i)
       number_of_points = pc.get_metadata().get('points')
       z = pc.pc_data['z']
       x = pc.pc_data['x']
       y = pc.pc_data['y']
       for j in range(number_of_points):
           glVertex3f(x[j], y[j], z[j])
   glEnd()

Main is:

files = glob.glob(os.getcwd() + "\\" + PCD_OutPutDirectory + "\*.pcd")

pygame.init()
display = (1700, 1000)
pygame.display.set_mode(display, DOUBLEBUF | OPENGL)
gluPerspective(50, (display[0] / display[1]), 0.1, 5000)
glTranslatef(0, 0, -1000)
Clock = pygame.time.Clock()
while True:
    Clock.tick(60)
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            pygame.quit()
            quit()
    glRotatef(2, 1, 1, 3)
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)
    PointClouds(files)
    pygame.display.flip()

prints all the points but between each rotation goes over all of the points and prints them again because there are 60k+ points it takes too much time between each rotation. i need it to read the points only once and freeze the image and not the rotation. thanks for the help

Rabbid76
  • 202,892
  • 27
  • 131
  • 174
Bob Sfog
  • 105
  • 8
  • Drawing by [`glBegin`/`glEnd`](https://www.khronos.org/registry/OpenGL-Refpages/gl2.1/xhtml/glBegin.xml) sequences and the fixed function matrix stack is deprecated since decades. See [Fixed Function Pipeline](https://www.khronos.org/opengl/wiki/Fixed_Function_Pipeline) and [Legacy OpenGL](https://www.khronos.org/opengl/wiki/Legacy_OpenGL). Read about [Vertex Specification](https://www.khronos.org/opengl/wiki/Vertex_Specification) and [Shader](https://www.khronos.org/opengl/wiki/Shader) for a state of the art way of rendering. – Rabbid76 Jun 27 '19 at 08:38
  • In case @Rabbid76's propositions cannot be applied (e.g. because you're limited to an old version), can you not just add a boolean that changes once you printed the data as well as a condition that triggers the reprint based on the boolean value? – Markus Deibel Jun 27 '19 at 08:40
  • @MarkusDeibel Why is he limited to an old version? I can't se any reason for that. – Rabbid76 Jun 27 '19 at 08:46
  • Maybe stuck on a _very_ old system, for whatever reason ¯\\_(ツ)_/¯ – Markus Deibel Jun 27 '19 at 08:49
  • 3
    @MarkusDeibel Ok, ma be. But the bottleneck is the for loop and the `glBegin`/`glEnd` sequence. Even on an old system buffers can be used, by client side capability and fixed function attributes. See [`glEnableClientState`](https://www.khronos.org/registry/OpenGL-Refpages/gl2.1/xhtml/glEnableClientState.xml) and [`glVertexPointer `](https://www.khronos.org/registry/OpenGL-Refpages/gl2.1/xhtml/glVertexPointer.xml). – Rabbid76 Jun 27 '19 at 08:52
  • @Rabbid76 So what is the most "efficient" method to render? – Noah Jun 27 '19 at 13:47
  • 1
    @Noah Put all to [Vertex buffers](https://www.khronos.org/opengl/wiki/Vertex_Specification#Vertex_buffer_offset_and_stride) at initialization (try to use large buffers rather than a lot of tiny buffers). Create as less [Shaders](https://www.khronos.org/opengl/wiki/Shader) as possible (try to use 1 shader). Try to change as less states as possible in the render loop and try to render as many meshes as possible with the same shader in a row. Try to avoid changes of the vertex buffer, just change uniforms like the model matrix. – Rabbid76 Jun 27 '19 at 13:54
  • How do you rotate? hope you are using one transform matrix for the point cloud and not rotating each point on your self in CPU side code that would be really too slow ... Single VBO can easily handle 300K-100000K points even on older setup with fixed function. Also you are accessing array 3 times per point that is crazy especially if it has own `[]` operator logic. Why not use pointer to float or double and use `glVertex3fv` or `glVertex3dv`. That will boost the speed 3 or more times depending on the `[]` overhead. Old and slow system? then why use python? – Spektre Jun 28 '19 at 09:06

1 Answers1

3

The bottleneck is the for loop and the glBegin/glEnd sequence. The data is read from the files in every frame. Note, that drawing by using glBegin/glEnd sequences and the fixed function matrix stack is deprecated since decades. Read the files once at start up and create a Vertex Buffer Object. (Read more about Vertex Specification and Shader for a state-of-the-art way of rendering.)

The closest solution to your existing code is to use client side capability glEnableClientState and fixed function attributes glVertexPointer. With this solution you don't need any shader program.
In the following I assume that you use PyOpenGL.

Load the vertex coordinates to an array

def LoadVertices(pcd_files):
    vertices = []
    for i in pcd_files:
       pc = pypcd.PointCloud.from_path(i)
       number_of_points = pc.get_metadata().get('points')
       z = pc.pc_data['z']
       x = pc.pc_data['x']
       y = pc.pc_data['y']
       for j in range(number_of_points):
           vertices += [x[j], y[j], z[j]]
    return vertices

Create a Vertex Buffer Object and create and initialize the buffer object's data store:

import ctypes
def CreateBuffer(vertices):
    bufferdata = (ctypes.c_float*len(vertices))(*vertices) # float buffer
    buffersize = len(vertices)*4                           # buffer size in bytes 

    vbo = glGenBuffers(1)
    glBindBuffer(GL_ARRAY_BUFFER, vbo)
    glBufferData(GL_ARRAY_BUFFER, buffersize, bufferdata, GL_STATIC_DRAW) 
    glBindBuffer(GL_ARRAY_BUFFER, 0)
    return vbo

Create a function which can draw the Point primitives from the buffer:

def DrawBuffer(vbo, noOfVertices):
    glBindBuffer(GL_ARRAY_BUFFER, vbo)
    glEnableClientState(GL_VERTEX_ARRAY)
    glVertexPointer(3, GL_FLOAT, 0, None)

    glDrawArrays(GL_POINTS, 0, noOfVertices)

    glDisableClientState(GL_VERTEX_ARRAY)
    glBindBuffer(GL_ARRAY_BUFFER, 0)

Use this functions in your program:

files = glob.glob(os.getcwd() + "\\" + PCD_OutPutDirectory + "\*.pcd")

pygame.init()
display = (1700, 1000)
pygame.display.set_mode(display, DOUBLEBUF | OPENGL)

vArray    = LoadVertices(files)
noPoints  = len(vArray) // 3
bufferObj = CreateBuffer(vArray)

gluPerspective(50, (display[0] / display[1]), 0.1, 5000)
glTranslatef(0, 0, -1000)
Clock = pygame.time.Clock()
while True:
    Clock.tick(60)
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            pygame.quit()
            quit()
    glRotatef(2, 1, 1, 3)
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)

    DrawBuffer(bufferObj, noPoints)

    pygame.display.flip()

If you wnat to add individual colors for each point, then vertex and its attribute doesn't only consists of the coordinates (x, y, z), it also has to have to RGB color (x, y, z, r, g, b), so each attribute tuple consists of 6 components rather than 3.

The fixed function color attribute has to be enabled by the client state GL_COLOR_ARRAY. Add the attribute is specified by glColorPointer.

Each attribute tuple has a size of 24 bytes, because a tuple consits of 6 components (x, y, z, r, g, b) and each component has a size of 4 bytes (this is the size of float).
This size has to be passed to 3rd parameter (sride) of glVertexPointer respectively glColorPointer.

If a named buffer object is bound, then the last parameter of glVertexPointer respectively glColorPointer is treated as byte offset into the buffer objects buffer store. The offset is the number of bytes to the 1st component of the attribute.
In case of glVertexPointer the offset is 0, because (x, y, z) are the first components in the attribute tuple. In case of glColorPointer the offset is 3*4=12 bytes, because (r, g, b) are after the 3 coordinates (x, y, z) and the size of each component is 4. Since the type of the last parameter is a pointer, the offset has to be casted to ctypes.c_void_p (e.g. ctypes.c_void_p(3*4)). For this python built-in library ctypes has to be imported. If the offset is 0, None can be used instead of ctypes.c_void_p(0).

The specification of the vertex attribtes may lookslike this:

glBindBuffer(GL_ARRAY_BUFFER, vbo)

stride = 6*4 # (24 bates) : [x, y, z, r, g, b] * sizeof(float)

glEnableClientState(GL_VERTEX_ARRAY)
glVertexPointer(3, GL_FLOAT, stride, None)

glEnableClientState(GL_COLOR_ARRAY)
offset = 3*4 # (12 bytes) : the rgb color starts after the 3 coordinates x, y, z 
glColorPointer(3, GL_FLOAT, stride, ctypes.c_void_p(offset))

All together:

import ctypes 

def LoadVertices(pcd_files):
    attributes = []
    for i in pcd_files:
       pc = pypcd.PointCloud.from_path(i)
       number_of_points = pc.get_metadata().get('points')
       z = pc.pc_data['z']
       x = pc.pc_data['x']
       y = pc.pc_data['y']
       r = # set the RGB color data here
       g =
       b = 
       for j in range(number_of_points):
           attributes += [x[j], y[j], z[j], r[j], g[j], b[j]]
    return attributes

def CreateBuffer(attributes):
    bufferdata = (ctypes.c_float*len(attributes))(*attributes) # float buffer
    buffersize = len(attributes)*4                             # buffer size in bytes 

    vbo = glGenBuffers(1)
    glBindBuffer(GL_ARRAY_BUFFER, vbo)
    glBufferData(GL_ARRAY_BUFFER, buffersize, bufferdata, GL_STATIC_DRAW) 
    glBindBuffer(GL_ARRAY_BUFFER, 0)
    return vbo

def DrawBuffer(vbo, noOfVertices):
    glBindBuffer(GL_ARRAY_BUFFER, vbo)

    stride = 6*4 # (24 bates) : [x, y, z, r, g, b] * sizeof(float)

    glEnableClientState(GL_VERTEX_ARRAY)
    glVertexPointer(3, GL_FLOAT, stride, None)

    glEnableClientState(GL_COLOR_ARRAY)
    offset = 3*4 # (12 bytes) : the rgb color starts after the 3 coordinates x, y, z 
    glColorPointer(3, GL_FLOAT, stride, ctypes.c_void_p(offset))

    glDrawArrays(GL_POINTS, 0, noOfVertices)

    glDisableClientState(GL_VERTEX_ARRAY)
    glDisableClientState(GL_COLOR_ARRAY)
    glBindBuffer(GL_ARRAY_BUFFER, 0)
files = glob.glob(os.getcwd() + "\\" + PCD_OutPutDirectory + "\*.pcd")

pygame.init()
display = (800, 600)
pygame.display.set_mode(display, DOUBLEBUF | OPENGL)

vArray    = LoadVertices(files)
noPoints  = len(vArray) // 6  # 6 components per attribute tuple
bufferObj = CreateBuffer(vArray)

gluPerspective(50, (display[0] / display[1]), 0.1, 5000)
glTranslatef(0, 0, -1000)
Clock = pygame.time.Clock()
while True:
    Clock.tick(60)
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            pygame.quit()
            quit()
    glRotatef(2, 1, 1, 3)
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)

    DrawBuffer(bufferObj, noPoints)

    pygame.display.flip()
Rabbid76
  • 202,892
  • 27
  • 131
  • 174
  • 1
    Thank you so much! works 100% looks so much better do you happen to know how can i add a color for each pixel also? is there a way to add it to the verticles list as a 4th column? – Bob Sfog Jun 28 '19 at 19:38