【问题标题】:How to create surface from 3-D numpy array in Pyopengl?如何从 Pyopengl 中的 3-D numpy 数组创建表面?
【发布时间】:2020-05-04 23:26:50
【问题描述】:

我创建了一个由 X、Y 和 Z 轴坐标组成的 3-d numpy 数组。现在我正在尝试在opengl中使用这些点创建一个表面,但我所取得的成功只是在下面创建了类似这样的线模型。任何人都可以建议更改我的代码以从数据中形成实际的 3-D 表面吗? 数据文件使用链接https://drive.google.com/open?id=1PWbNIt3xbchtQ9HIIS96k7ZjblzPO_wO

代码:-

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()

【问题讨论】:

    标签: python-3.x numpy pyqt5 pyopengl python-moderngl


    【解决方案1】:

    'three.npy' 包含一个 3 维数组 (7782 x 24 x3),其中包含管的顶点坐标。大小为 3 的第 3 维包含顶点的 xyz 坐标。顶点被组织成 7782 个环,圆周上有 24 个点。

    将顶点坐标读取到展平缓冲区(numpy 数组由glBufferData 自动展平)。
    生成索引数组(顶点缓冲区的索引)。索引描述了堆叠 7781 个环的GL_TRIANGLE_STRIP 原语。每个环由圆周上的 24 个四边形组成。

    self.e = np.load(('three.npy'), mmap_mode='r')
    self.e = np.array(self.e, dtype=np.float32)
    
    self.elems = []
    ring_c = self.e.shape[1]
    slice_c = self.e.shape[0]
    for si in range(slice_c-1):
        self.elems += [si*ring_c, si*ring_c]
        for ri in range(ring_c+1):
            ie = ri % ring_c
            self.elems += [ie+si*ring_c, ie+(si+1)*ring_c]
    self.elems = np.array(self.elems, dtype=np.int32)
    

    顶点的 xy 分量在 [-10, 10] 范围内,但 z 分量在 [3, 2972​​4672] 范围内。

    x min      x max     y min      y max     z min      z max
    -10.589109 10.517833 -10.464569 10.594374 29724672.0 3.1618009
    

    我建议为 z 坐标定义一个比例:

    self.scaleZ = 0.000001
    

    为顶点创建一个Vertex Buffer Object (GL_ARRAY_BUFFER),为索引创建一个Index buffer Object (GL_ELEMENT_ARRAY_BUFFER):

    self.vertexbuffer = gl.glGenBuffers(1)
    gl.glBindBuffer(gl.GL_ARRAY_BUFFER, self.vertexbuffer)
    gl.glBufferData(gl.GL_ARRAY_BUFFER, self.e, gl.GL_DYNAMIC_DRAW)
    
    self.elementbuffer = gl.glGenBuffers(1)
    gl.glBindBuffer(gl.GL_ELEMENT_ARRAY_BUFFER, self.elementbuffer)
    gl.glBufferData(gl.GL_ELEMENT_ARRAY_BUFFER, self.elems, gl.GL_DYNAMIC_DRAW)
    

    指定顶点坐标数组。见Vertex SpecificationglVertexAttribPointerstride 和偏移量parameter 必须为 0。
    stride 指定其具有的连续通用顶点属性之间的字节偏移量为3*self.e.itemsize (12) 或0。 0 具有特殊含义,并将属性解释为紧密包装。如果 stride 为 0,则由 sizetype 参数计算。
    offset 必须为 @ 987654348@或None,因为数组中1属性的偏移量为0。
    在任何情况下,strideoffset 的单位都是字节。

    gl.glBindBuffer(gl.GL_ARRAY_BUFFER, self.vertexbuffer)
    stride = 0 # 3*self.e.itemsize
    offset = None # ctypes.c_void_p(0)
    loc = self.attrib['position']
    gl.glEnableVertexAttribArray(loc)
    gl.glVertexAttribPointer(loc, 3, gl.GL_FLOAT, False, stride, offset)
    

    primitive 类型为GL_TRIANGLE_STRIP,在glDrawElements 绘制元素之前必须绑定索引缓冲区:

    gl.glBindBuffer(gl.GL_ELEMENT_ARRAY_BUFFER, self.elementbuffer)
    
    self.perspective_view()
    gl.glUniform3f(self.uniform['triangleColor'], 1, 1, 1)
    gl.glDrawElements(gl.GL_TRIANGLE_STRIP, self.elems.size, gl.GL_UNSIGNED_INT, None)
    

    我建议不要在顶点着色器中指定Orthographic projection 矩阵,而是使用矩阵Uniform variable 进行投影,分别用于模型和视图转换。
    投影矩阵定义了 3 维视域到 2 维视口的投影。视图矩阵定义了视图的查看位置和对场景的查看方向。模型矩阵定义了模式的比例和动画。

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

    获取着色器程序链接后的属性索引和统一位置:

    vs = compile_vertex_shader(VS)
    fs = compile_fragment_shader(FS)
    self.shaders_program = link_shader_program(vs, fs)
    self.attrib = { a : gl.glGetAttribLocation (self.shaders_program, a) for a in ['position'] }
    print(self.attrib)
    self.uniform = { u : gl.glGetUniformLocation (self.shaders_program, u) for u in ['u_model', 'u_view', 'u_proj', "triangleColor"] }
    print(self.uniform)
    

    对于 3D 外观,我建议使用 Perspective projection 而不是 Orthographic projection
    使用numpy.arraynumpy.matrix 设置矩阵。

    # projection matrix
    aspect, ta, near, far = self.width/self.height, np.tan(np.radians(90.0) / 2), 0.1, 50
    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, -1, 0), (0, 1, 0, 0), (0, 0, -30, 1)), np.float32)
    
    # model matrix
    c, s = math.cos(self.angle), math.sin(self.angle)
    model = scale
    
    gl.glUniformMatrix4fv(self.uniform['u_proj'], 1, gl.GL_FALSE, proj)
    gl.glUniformMatrix4fv(self.uniform['u_view'], 1, gl.GL_FALSE, view)
    gl.glUniformMatrix4fv(self.uniform['u_model'], 1, gl.GL_FALSE, model)
    

    完整示例:
    (片段着色器根据片段的深度对片段进行着色)

    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
    import os
    import math
    
    sourceFileDir = os.path.dirname(os.path.abspath(__file__))
    
    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 mat4 u_proj;
    uniform mat4 u_view;
    uniform mat4 u_model;
    
    void main() {
    
        gl_Position = u_proj * u_view * u_model * vec4(position, 1.0);
    }
    '''
    
    FS = '''
    #version 450
    out vec4 outColor;
    
    uniform vec3 triangleColor;
    
    void main()
    {
        float d = 1.0 - gl_FragCoord.z;
        outColor = vec4(triangleColor * d, 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 = compile_vertex_shader(VS)
            fs = compile_fragment_shader(FS)
            self.shaders_program = link_shader_program(vs, fs)
            self.attrib = { a : gl.glGetAttribLocation (self.shaders_program, a) for a in ['position'] }
            print(self.attrib)
            self.uniform = { u : gl.glGetUniformLocation (self.shaders_program, u) for u in ['u_model', 'u_view', 'u_proj', "triangleColor"] }
            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)
            print(self.e.shape)
    
            self.elems = []
            ring_c = self.e.shape[1]
            slice_c = self.e.shape[0]
            for si in range(slice_c-1):
                self.elems += [si*ring_c, si*ring_c]
                for ri in range(ring_c+1):
                    ie = ri % ring_c
                    self.elems += [ie+si*ring_c, ie+(si+1)*ring_c]
    
            self.elems = np.array(self.elems, dtype=np.int32)
    
            self.vertexbuffer = gl.glGenBuffers(1)
            gl.glBindBuffer(gl.GL_ARRAY_BUFFER, self.vertexbuffer)
            gl.glBufferData(gl.GL_ARRAY_BUFFER, self.e, gl.GL_DYNAMIC_DRAW)
    
            self.elementbuffer = gl.glGenBuffers(1)
            gl.glBindBuffer(gl.GL_ELEMENT_ARRAY_BUFFER, self.elementbuffer)
            gl.glBufferData(gl.GL_ELEMENT_ARRAY_BUFFER, self.elems, gl.GL_DYNAMIC_DRAW)
    
            self.scaleZ = 0.000001
            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()
            print(self.right, self.left, self.top, self.bottom, self.far, self.near)
            self.far *= self.scaleZ
            self.near *= self.scaleZ
            self.angle = 0.0
    
            self.GLtimer = QTimer()
            self.GLtimer.setInterval(10)
            self.GLtimer.timeout.connect(self.redraw)
            self.GLtimer.start()
    
        def redraw(self):
            self.angle += 0.01
            self.update()
    
        def perspective_view(self):
    
            # projection matrix
            aspect, ta, near, far = self.width/self.height, np.tan(np.radians(90.0) / 2), 10, 50
            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, -1, 0), (0, 1, 0, 0), (0, 0, -30, 1)), np.float32)
    
            # model matrix
            c, s = math.cos(self.angle), math.sin(self.angle)
            scale = np.matrix(((1, 0, 0, 0), (0, 1, 0, 0), (0, 0, self.scaleZ, 0), (0, 0, 0, 1)), np.float32)
            rotZ = np.array(((c, s, 0, 0), (-s, c, 0, 0), (0, 0, 1, 0), (0, 0, 0, 1)), np.float32) 
            rotY = np.matrix(((0, 0, 1, 0), (0, 1, 0, 0), (-1, 0, 0, 0), (0, 0, 0, 1)), np.float32)
            trans = np.matrix(((1, 0, 0, 0), (0, 1, 0, 0), (0, 0, 1, 0), (0, 0, (self.near - self.far)/2, 1)), np.float32)
            model = scale * trans * rotY * rotZ
    
            gl.glUniformMatrix4fv(self.uniform['u_proj'], 1, gl.GL_FALSE, proj)
            gl.glUniformMatrix4fv(self.uniform['u_view'], 1, gl.GL_FALSE, view)
            gl.glUniformMatrix4fv(self.uniform['u_model'], 1, gl.GL_FALSE, model)
    
    
        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.GL_DEPTH_BUFFER_BIT)
            gl.glEnable(gl.GL_DEPTH_TEST)
            gl.glUseProgram(self.shaders_program)
    
            gl.glBindBuffer(gl.GL_ARRAY_BUFFER, self.vertexbuffer)
            stride = 0 # 3*self.e.itemsize
            offset = None # ctypes.c_void_p(0)
            loc = self.attrib['position']
            gl.glEnableVertexAttribArray(loc)
            gl.glVertexAttribPointer(loc, 3, gl.GL_FLOAT, False, stride, offset)
    
            gl.glBindBuffer(gl.GL_ELEMENT_ARRAY_BUFFER, self.elementbuffer)
    
            self.perspective_view()
            gl.glUniform3f(self.uniform['triangleColor'], 1, 1, 1)
            #gl.glPolygonMode(gl.GL_FRONT_AND_BACK, gl.GL_LINE)
            gl.glDrawElements(gl.GL_TRIANGLE_STRIP, self.elems.size, gl.GL_UNSIGNED_INT, None)
    
        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()
    

    【讨论】:

    • 此问题已解决。你也可以告诉我更多关于这个闪电如何在圆柱体表面上使用它,这样如果有任何三角形超出平均直径,它就会有不同的颜色
    • @AKS 在此示例中不是照明。碎片的颜色取决于那里的深度。对于光照模型,您必须(在 CPU 上)计算片段的法线向量,并为法线向量指定一个附加属性(每个顶点坐标有 1 个法线向量)。
    • 所以我的理解是我必须创建一个颜色的一维数组并将其传递给片段着色器。但我不明白片段着色器中的代码是什么,数组的长度将等于数据点的数量或我们正在渲染的三角形的数量?
    • @AKS 调查绘图调用gl.glDrawElements(gl.GL_TRIANGLE_STRIP, self.elems.size, gl.GL_UNSIGNED_INT, None)self.elems.size 是第 th 个索引。由于基元类型gl.GL_TRIANGLE_STRIP,索引列表被解释为三角形基元列表。如果要使用gl.glDrawArrays,则必须传递顶点数。
    猜你喜欢
    • 1970-01-01
    • 2021-01-20
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-11-23
    • 1970-01-01
    • 1970-01-01
    • 2018-07-13
    相关资源
    最近更新 更多