【问题标题】:Reading depth buffer with PyOpenGL使用 PyOpenGL 读取深度缓冲区
【发布时间】:2018-06-19 15:45:11
【问题描述】:

基本上,我正在尝试在渲染模型(脚本为从文件加载模型,路径指定为第一个命令行参数)。

我想到了几个问题:

  • 为什么glReadPixels 调用返回一个形状为(width, shape) 的numpy 数组,而不是(height, width)

  • 为什么它返回一些垃圾,没有连接到渲染模型?

  • 有没有一种简单的方法可以使用 PyOpenGL 框架在 OpenGL 旧代码上获取 z 坐标?

  • 我可以在这里得到的最大值是某个范围为[0; 1] 的数组是否正确,基本上是zNear 和zFar 之间的某个分数(无论出于何种原因,由glReadPixels 标准化)?

代码本身:

import sys
import argparse
import pyassimp
from pyassimp.postprocess import aiProcess_JoinIdenticalVertices, aiProcess_Triangulate
import numpy as np
import matplotlib.pyplot as plt
from collections import namedtuple
from OpenGL import GL, GLUT

Mesh = namedtuple('Mesh', ('vertices', 'faces'))

def load_mesh(filename):
    scene = pyassimp.load(filename, processing=aiProcess_JoinIdenticalVertices | aiProcess_Triangulate)
    mesh = scene.mMeshes[0].contents

    def get_vector_array(vector):
        return [vector.x, vector.y, vector.z]

    def get_face_array(face):
        return [face.mIndices[i] for i in xrange(face.mNumIndices)]

    vertices = np.array([get_vector_array(mesh.mVertices[i]) for i in xrange(mesh.mNumVertices)])
    faces = np.array([get_face_array(mesh.mFaces[i]) for i in xrange(mesh.mNumFaces)])

    pyassimp.release(scene)

    return Mesh(vertices, faces)


def load_ortho():
    GL.glMatrixMode(GL.GL_PROJECTION)
    GL.glLoadIdentity()
    GL.glOrtho(-1, 1, -1, 1, -1, 1)
    GL.glMatrixMode(GL.GL_MODELVIEW)
    GL.glLoadIdentity()


mesh = None
width, height = 1920, 1080

def draw_mesh():
    global mesh, width, height
    GL.glClearColor(0, 0, 0, 0)
    GL.glClearDepth(0.5)
    GL.glClear(GL.GL_COLOR_BUFFER_BIT | GL.GL_DEPTH_BUFFER_BIT)
    GL.glDepthMask(GL.GL_TRUE)
    load_ortho()
    for face in mesh.faces:
        GL.glBegin(GL.GL_POLYGON)
        for vertex in mesh.vertices[face]:
            GL.glVertex3dv(vertex)
        GL.glEnd()
    GLUT.glutSwapBuffers()
    d = GL.glReadPixels(0, 0, width, height, GL.GL_DEPTH_COMPONENT, GL.GL_FLOAT)
    plt.imshow(d)
    plt.show()


def reshape(w, h):
    GL.glViewport(0, 0, w, h)
    GLUT.glutDisplayFunc(draw_mesh)
    GLUT.glutPostRedisplay()


def init(width, height):
    GLUT.glutInit(sys.argv)
    GLUT.glutInitDisplayMode(GLUT.GLUT_RGBA | GLUT.GLUT_DOUBLE)
    GLUT.glutInitWindowSize(width, height)
    GLUT.glutInitWindowPosition(0, 0)
    GLUT.glutCreateWindow("test")
    # GLUT.glutDisplayFunc(draw_mesh)
    # GLUT.glutIdleFunc(draw_mesh)
    GLUT.glutReshapeFunc(reshape)
    GLUT.glutIdleFunc(GLUT.glutPostRedisplay)

    def keyPressed(self, *args):
        if args[0] == '\033':
            sys.exit()
    GLUT.glutKeyboardFunc(keyPressed)


if __name__ == '__main__':
    parser = argparse.ArgumentParser("Test on extracting depth while rendering a model with PyOpenGL")
    parser.add_argument("model", type=str)
    args = parser.parse_args()
    global mesh
    mesh = load_mesh(args.model)

    init(width, height)
    draw_mesh()

我个人用于测试的模型文件:bunny.obj sn-p 的结果是here

【问题讨论】:

  • 在正交投影中,Z 坐标被线性映射到深度值(参见this question)。深度值在 [0.0, 1.0] 范围内。所以视图空间中的 Z 坐标是z = -near - depth*(far-near)。在您的情况下,它是z = 1 - 2*depth;。因为您没有设置任何模型视图矩阵(GL_MODELVIEW),所以视图空间等于世界空间。
  • 也可能是愚蠢的问题:看起来您没有在任何地方调用glClear 来清除颜色和深度缓冲区。 glClearColorglClearDepth 只是设置清除应该执行的值,但实际上并不执行缓冲区本身的清除。这或许可以解释您所看到的“垃圾”。
  • @CodeSurgeon 这没有帮助。相应地更新了 sn-p。还添加了 sn-p 的输出。

标签: python opengl pyopengl


【解决方案1】:

运行您的代码给了我一些"Invalid Operation Error: 1282" 消息,用于glReadPixels 调用。相反,这是我刚刚编写的一个简单演示,它展示了如何从 OpenGL 获取渲染三角形的颜色和深度缓冲区。我在这里所做的是将 FBO(帧缓冲区对象)与所需的纹理附件(用于接收颜色和深度数据)绑定到屏幕。然后我使用glGetTexImage 从 GPU 读取数据。使用纹理可能不是最快的方法,但这很简单,应该可以很好地工作。如果其中有任何不清楚的地方,请告诉我,我会详细说明。

from OpenGL.GL import *
from OpenGL.GLUT import *
import numpy as np
import sys

def draw_scene():
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)
    glBegin(GL_TRIANGLES)
    glColor3f(1, 0, 0)
    glVertex3f(-1, -1, 0)
    glColor3f(0, 1, 0)
    glVertex3f(0, 1, 0)
    glColor3f(0, 0, 1)
    glVertex3f(1, -1, 0)
    glEnd()

def draw_texture():
    global color_texture
    glColor3f(1, 1, 1)
    glBindTexture(GL_TEXTURE_2D, color_texture)
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)
    glBegin(GL_QUADS)
    glTexCoord2f(0, 0)
    glVertex3f(-1, -1, 0)
    glTexCoord2f(0, 1)
    glVertex3f(-1, 1, 0)
    glTexCoord2f(1, 1)
    glVertex3f(1, 1, 0)
    glTexCoord2f(1, 0)
    glVertex3f(1, -1, 0)
    glEnd()
    glBindTexture(GL_TEXTURE_2D, 0)

def update_display():
    global fbo, color_texture, depth_texture

    #Render the scene to an offscreen FBO
    glBindFramebuffer(GL_FRAMEBUFFER, fbo)
    draw_scene()
    glBindFramebuffer(GL_FRAMEBUFFER, 0)

    #Then render the results of the color texture attached to the FBO to the screen
    draw_texture()

    #Obtain the color data in a numpy array
    glBindTexture(GL_TEXTURE_2D, color_texture)
    color_str = glGetTexImage(GL_TEXTURE_2D, 0, GL_RGBA, GL_UNSIGNED_BYTE)
    glBindTexture(GL_TEXTURE_2D, 0)
    color_data = np.fromstring(color_str, dtype=np.uint8)

    #Obtain the depth data in a numpy array
    glBindTexture(GL_TEXTURE_2D, depth_texture)
    depth_str = glGetTexImage(GL_TEXTURE_2D, 0, GL_DEPTH_COMPONENT, GL_FLOAT)
    glBindTexture(GL_TEXTURE_2D, 0)
    depth_data = np.fromstring(depth_str, dtype=np.float32)
    print(np.min(depth_data), np.max(depth_data))#This is just to show the normalized range of depth values obtained

    glutSwapBuffers()

width, height = 800, 600
fbo = None
color_texture = None
depth_texture = None

if __name__ == '__main__':
    glutInit([])
    glutInitDisplayMode(GLUT_RGBA | GLUT_DOUBLE)
    glutInitWindowSize(width, height)
    glutInitWindowPosition(0, 0)
    glutCreateWindow("Triangle Test")

    glEnable(GL_TEXTURE_2D)#not needed if using shaders...
    glEnable(GL_DEPTH_TEST)

    color_texture = glGenTextures(1)
    glBindTexture(GL_TEXTURE_2D, color_texture)
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST)
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST)
    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, None)
    glBindTexture(GL_TEXTURE_2D, 0)

    depth_texture = glGenTextures(1)
    glBindTexture(GL_TEXTURE_2D, depth_texture)
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST)
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST)
    glTexImage2D(GL_TEXTURE_2D, 0, GL_DEPTH_COMPONENT, width, height, 0, GL_DEPTH_COMPONENT, GL_FLOAT, None)
    glBindTexture(GL_TEXTURE_2D, 0)

    fbo = glGenFramebuffers(1)
    glBindFramebuffer(GL_FRAMEBUFFER, fbo)
    glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, color_texture, 0)
    glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_2D, depth_texture, 0)
    glBindFramebuffer(GL_FRAMEBUFFER, 0)

    glutDisplayFunc(update_display)
    glutIdleFunc(glutPostRedisplay)
    glutMainLoop()

【讨论】:

  • 我认为您的答案不适用:1)绘图仅为 2D,不存在深度范围。 2)您是否尝试以这种方式渲染模型?抱歉,还没有时间彻底检查您的方法(并将其应用于我的任务)。另一个问题:你在什么平台上运行我的 sn-p(它因运行时错误而失败)?
  • @AndreyKravtsun 1) 即使绘图是二维的,它仍然应该是相关的。如果查看depth_data 的内容,可以看到背景深度和三角形深度。我只是选择了一个 2D 三角形,因为这是一个简单的形状,可以用非常少的线条进行渲染。随意更改三角形的 z 索引并查看深度纹理中的值。 2) 模型也应该可以正常工作,只是没有在我的笔记本电脑上安装Assimp 库。 3) 在我的笔记本电脑上使用 LinuxMint 18.3。
  • 但是问题之一仍然存在,像素读取函数返回错误尺寸(宽度和高度交换?)是如何发生的。例如,在你的代码中 sn -p depth_str = glGetTexImage(GL_TEXTURE_2D, 0, GL_DEPTH_COMPONENT, GL_FLOAT)depth_str.shape == (800, 600),根据调试输出。
  • @AndreyKravtsun 这是一个很好的观点!从与 pyopengl 的 glGetTexImage 相关联的手动文档字符串(请参阅here),它似乎将GL_UNSIGNED_BYTE 数据与其他类型的数据(它在幕后处理成一个 numpy大批)。我怀疑他们“翻转”了数据,因此depth_str[x_coordinate, y_coordinate] 的索引是“直观的”。 pyopengl github上的OpenGL/images.py源码不好摸。
猜你喜欢
  • 1970-01-01
  • 2020-07-13
  • 2011-02-05
  • 2021-05-06
  • 2013-08-03
  • 1970-01-01
  • 1970-01-01
  • 2011-11-11
  • 1970-01-01
相关资源
最近更新 更多