How to create surface from 3-D numpy array in Pyopengl?

I have created a 3-d numpy array consisting of X, Y, and Z axis coordinates. Now I am trying to create a surface using these points in opengl but all I have got success is in creating wire model something like this below model. Can anyone suggest changes in my code to form actual 3-D surface from data? Data file used link https://drive.google.com/open?id=1PWbNIt3xbchtQ9HIIS96k7ZjblzPO_wO

Code:-

import OpenGL.GL as gl
import OpenGL.arrays.vbo as glvbo
from PyQt5.Qt import *
import numpy as np
import Backend_algo as Sb
import sys
import ctypes


def compile_vertex_shader(source):
    """Compile a vertex shader from source."""
    vertex_shader = gl.glCreateShader(gl.GL_VERTEX_SHADER)
    gl.glShaderSource(vertex_shader, source)
    gl.glCompileShader(vertex_shader)
    # check compilation error
    result = gl.glGetShaderiv(vertex_shader, gl.GL_COMPILE_STATUS)
    if not (result):
        raise RuntimeError(gl.glGetShaderInfoLog(vertex_shader))
    return vertex_shader


def compile_fragment_shader(source):
    """Compile a fragment shader from source."""
    fragment_shader = gl.glCreateShader(gl.GL_FRAGMENT_SHADER)
    gl.glShaderSource(fragment_shader, source)
    gl.glCompileShader(fragment_shader)
    result = gl.glGetShaderiv(fragment_shader, gl.GL_COMPILE_STATUS)
    if not (result):
        raise RuntimeError(gl.glGetShaderInfoLog(fragment_shader))
    return fragment_shader


def link_shader_program(vertex_shader, fragment_shader):
    """Create a shader program with from compiled shaders."""
    program = gl.glCreateProgram()
    gl.glAttachShader(program, vertex_shader)
    gl.glAttachShader(program, fragment_shader)
    gl.glLinkProgram(program)

    result = gl.glGetProgramiv(program, gl.GL_LINK_STATUS)
    if not (result):
        raise RuntimeError(gl.glGetProgramInfoLog(program))
    return program


VS = '''

attribute vec3 position;


uniform float right;
uniform float bottom;
uniform float left;
uniform float top;
uniform float far;
uniform float near;

void main() {

    mat4 testmat = mat4(
            vec4(2.0 / (right - left), 0, 0, 0),
            vec4(0, 2.0 / (top - bottom), 0, 0),
            vec4(0, 0, -2.0 / (far - near), 0),
            vec4(-(right + left) / (right - left), -(top + bottom) / (top - bottom), -(far + near) / (far - near), 1)
    );

    gl_Position = testmat * vec4(position, 1.);

}

'''
FS = '''
#version 450
// Output variable of the fragment shader, which is a 4D vector containing the
// RGBA components of the pixel color.
uniform vec3 triangleColor;
out vec4 outColor;

void main()
{
    outColor = vec4(triangleColor, 1.0);
}
'''


class GLPlotWidget3D(QGLWidget):

    def __init__(self, *args):
        # QGLWidget.__init__(self)
        super(GLPlotWidget3D, self).__init__()
        # self.parent = args[0]
        self.width, self.height = 100, 100
        self.right, self.left, self.top, self.bottom = 21000, -21000, 10, -10
        self.data = np.zeros((3, 10, 2))
        self.vbo = glvbo.VBO(self.data)

        self.showMaximized()

    def initializeGL(self):
        vs = Sb.compile_vertex_shader(VS)
        fs = Sb.compile_fragment_shader(FS)
        self.shaders_program = link_shader_program(vs, fs)
        self.e = np.load(('three.npy'), mmap_mode='r')
        self.e = np.array(self.e, dtype=np.float32)

        self.right, self.left, self.top, self.bottom, self.far, self.near = self.e[:, :, 1].min(), self.e[:, : , 1].max(), self.e[:, : , 0].min(), self.e[:, : , 0].max(), self.e[:, : , 2].max(), self.e[:, : , 2].min()

    def ortho_view(self):
        right = gl.glGetUniformLocation(self.shaders_program, "right")
        gl.glUniform1f(right, self.right)

        left = gl.glGetUniformLocation(self.shaders_program, "left")
        gl.glUniform1f(left, self.left)

        top = gl.glGetUniformLocation(self.shaders_program, "top")
        gl.glUniform1f(top, self.top)

        bottom = gl.glGetUniformLocation(self.shaders_program, "bottom")
        gl.glUniform1f(bottom, self.bottom)

        far = gl.glGetUniformLocation(self.shaders_program, "far")
        gl.glUniform1f(far, self.far)

        near = gl.glGetUniformLocation(self.shaders_program, "near")
        gl.glUniform1f(near, self.near)


    def paintGL(self):
        self.resizeGL(self.width, self.height)
        gl.glClearColor(0.2, 0.2, 0.2, 0)
        gl.glClear(gl.GL_COLOR_BUFFER_BIT)
        gl.glUseProgram(self.shaders_program)

        buffer = gl.glGenBuffers(1)

        gl.glBindBuffer(gl.GL_ARRAY_BUFFER, buffer)
        stride = self.e.strides[0]
        offset = ctypes.c_void_p(1)
        loc = gl.glGetAttribLocation(self.shaders_program, "position")
        gl.glEnableVertexAttribArray(loc)
        gl.glVertexAttribPointer(loc, 3, gl.GL_FLOAT, False, stride, offset)
        gl.glBufferData(gl.GL_ARRAY_BUFFER, self.e.nbytes, self.e, gl.GL_DYNAMIC_DRAW)

        gl.glDrawArrays(gl.GL_LINE_LOOP, 0, self.e.shape[0])

        self.ortho_view()
        uni_color = gl.glGetUniformLocation(self.shaders_program, "triangleColor")
        gl.glUniform3f(uni_color, 0.9, 0.9, 0.9)


    def resizeGL(self, width, height):
        self.width, self.height = width, height
        gl.glViewport(0, 0, width, height)


def main():
    app = QApplication(sys.argv)
    editor = GLPlotWidget3D()
    editor.show()
    sys.exit(app.exec_())


if __name__ == '__main__':
    main()

1 answer

  • answered 2020-01-18 12:01 Rabbid76

    I recommend to use a view and projection matrix:

    attribute vec3 position;
    
    uniform mat4 u_proj;
    uniform mat4 u_view;
    
    void main() {
    
        gl_Position = u_proj * u_view * vec4(position, 1.);
    }
    

    Get the uniform locations ans use numpy.array or numpy.matrix to set the matrices. e.g:

    class GLPlotWidget3D(QGLWidget):
        # [...]
    
        def initializeGL(self):
    
            vs = compile_vertex_shader(VS)
            fs = compile_fragment_shader(FS)
            self.shaders_program = link_shader_program(vs, fs)
            self.uniform = { u : gl.glGetUniformLocation (self.shaders_program, u) for u in ['u_view', 'u_proj'] }
            print(self.uniform)
    
            self.e = np.load((os.path.join(sourceFileDir,'three.npy')), mmap_mode='r')
            self.e = np.array(self.e, dtype=np.float32)
    
            self.right, self.left, self.top, self.bottom, self.far, self.near = self.e[:, :, 1].min(), self.e[:, : , 1].max(), self.e[:, : , 0].min(), self.e[:, : , 0].max(), self.e[:, : , 2].max(), self.e[:, : , 2].min()
    
        def ortho_view(self):
    
            # projection matrix
            r, l, t, b, f, n = self.right, self.left, self.top, self.bottom, self.far, self.near
            proj = np.matrix((
                (2/(r-l), 0, 0, 0),
                (0, 2/(t-b), 0, 0),
                (0, 0, -2/(f-n), -1),
                (-(r+l)/(r-l), -(t+b)/(t-b), -(f+n)/(f-n), 0)),
                np.float32)
    
            # view matrix
            view = np.matrix(((1, 0, 0, 0), (0, 1, 0, 0), (0, 0, 1, 0), (0, 0, -10, 1)), np.float32)
    
            gl.glUniformMatrix4fv(self.uniform['u_proj'], 1, gl.GL_FALSE, proj)
            gl.glUniformMatrix4fv(self.uniform['u_view'], 1, gl.GL_FALSE, view)
    

    The stride and offest parameter of glVertexAttribPointer have to be 0.
    stride specifies the byte offset between consecutive generic vertex attributes it has to be either 3*self.e.itemsize (12) or 0. 0 has a special meaning and and interprets the attributes as to be tightly packed. If stride is 0 it is computed by the size and type parameter.
    offset hast to be ctypes.c_void_p(0) or None, because the offset of the 1 attribute in the array is 0.
    In any case the unit of stride and offset is bytes.

    Furthermore the mesh consists of triangles. The primitive type has to be a GL_TRIANGLES (See also Triangle primitives):

    class GLPlotWidget3D(QGLWidget):
        # [...]
    
        def paintGL(self):
            self.resizeGL(self.width, self.height)
            gl.glClearColor(0.2, 0.2, 0.2, 0)
            gl.glClear(gl.GL_COLOR_BUFFER_BIT)
            gl.glUseProgram(self.shaders_program)
    
            buffer = gl.glGenBuffers(1)
    
            gl.glBindBuffer(gl.GL_ARRAY_BUFFER, buffer)
            stride = 0 # 3*self.e.itemsize
            offset = None # ctypes.c_void_p(0)
            loc = gl.glGetAttribLocation(self.shaders_program, "position")
            gl.glEnableVertexAttribArray(loc)
            gl.glVertexAttribPointer(loc, 3, gl.GL_FLOAT, False, stride, offset)
            gl.glBufferData(gl.GL_ARRAY_BUFFER, self.e, gl.GL_DYNAMIC_DRAW)
    
            self.ortho_view()
            uni_color = gl.glGetUniformLocation(self.shaders_program, "triangleColor")
            gl.glUniform3f(uni_color, 0.9, 0.9, 0.9)
    
            gl.glDrawArrays(gl.GL_TRIANGLES, 0, self.e.shape[0] * self.e.shape[1])
    

    For a 3D look, I recommend to use Perspective projection rather than Orthographic projection. T

    def perspective_view(self):
    
            # projection matrix
            aspect, ta, near, far = self.width/self.height, np.tan(np.radians(90.0) / 2), 0.1, self.far
            proj = np.matrix(((1/ta/aspect, 0, 0, 0), (0, 1/ta, 0, 0), (0, 0, -(far+near)/(far-near), -1), (0, 0, -2*far*near/(far-near), 0)), np.float32)
    
            # view matrix
            view = np.matrix(((1, 0, 0, 0), (0, 0.707, -0.707, 0), (0, 0.707, 0.707, 0), (0, 0, -20, 1)), np.float32)
    
            gl.glUniformMatrix4fv(self.uniform['u_proj'], 1, gl.GL_FALSE, proj)
            gl.glUniformMatrix4fv(self.uniform['u_view'], 1, gl.GL_FALSE, view)