Python 如何将PYopenGL写入JPG图像

Python 如何将PYopenGL写入JPG图像,python,image,pyopengl,wavefront,Python,Image,Pyopengl,Wavefront,我是PYopenGL的新手, 事实上,我也不确定PYopenGL是否适合我的任务 我有一个波阵面obj文件格式的3D模型。我需要从给定的视图中获取模型的“打印屏幕”。换句话说,我需要渲染模型,而不是显示模型,将其另存为图像(jpg) 我的想法是使用PYopenGL完成这项任务。然而,通过谷歌搜索,我找不到任何建议或例子来说明如何做到这一点。因此,我开始怀疑PYopenGL是否适合我的任务 你们中有人已经有了类似的东西,或者知道一个我可以用来学习的例子吗 提前谢谢 Michi只要您不介意打开并运行

我是PYopenGL的新手, 事实上,我也不确定PYopenGL是否适合我的任务

我有一个波阵面obj文件格式的3D模型。我需要从给定的视图中获取模型的“打印屏幕”。换句话说,我需要渲染模型,而不是显示模型,将其另存为图像(jpg)

我的想法是使用PYopenGL完成这项任务。然而,通过谷歌搜索,我找不到任何建议或例子来说明如何做到这一点。因此,我开始怀疑PYopenGL是否适合我的任务

你们中有人已经有了类似的东西,或者知道一个我可以用来学习的例子吗

提前谢谢


Michi

只要您不介意打开并运行OpenGL窗口,只要您感兴趣的是从特定角度将3d模型/场景渲染到图像,就可以将PyOpenGL用于您的目的

在线上有许多资源讨论如何为.obj格式编写解析器。除了查看wikipedia关于该格式的文章外,我相信您还可以在上找到一个固定函数obj加载器的实现。如果您自己制作.obj模型,那么会更容易,因为规范非常松散,并且很难编写健壮的解析器。或者,您可以使用类似于
Assimp
的库来加载模型并提取它们的数据,其中包含python
pyAssimp
绑定

至于保存屏幕截图,您需要将3D场景渲染为纹理。我强烈建议大家看一看。如果要进行屏幕外渲染,您需要了解如何使用glReadPixels以及如何使用FBO(帧缓冲区对象)。

我的答案(部分基于)是关于问题的第二部分

在PyOpenGL中的屏幕外渲染(意味着将某些内容渲染到内部缓冲区而不是可视窗口,并将渲染图像保存到文件或作为http响应传输到网页上显示)有点棘手,因为到目前为止GLUT所做的一切(创建窗口、初始化OpenGL上下文等)都需要手工完成,因为你不需要标准的GLUT窗口来弹出甚至闪烁

因此,OpenGL中的屏幕外渲染有3种方法:

1) 使用GLUT进行初始化,但隐藏GLUT窗口并对其进行渲染。此方法完全独立于平台,但GLUT窗口在初始化过程中出现的时间很短,因此它不太适合web服务器,也就是说,您仍然可以将其设置为单独的web服务器,仅在启动时进行初始化,并使用一些接口与之通信

2) 手动创建所有内容:隐藏窗口、OpenGL上下文和要渲染的帧缓冲区对象。此方法很好,因为您可以控制所有内容,并且不会显示任何窗口,但上下文的创建是特定于平台的(下面是Win64的示例)

3) 第三种方法类似于方法2,但使用由WGL创建的默认帧缓冲区,而不是手动创建FBO。效果与方法2相同,但更简单。如果你因为其他原因不需要FBO,可能是更好的选择

现在我将描述方法2,核心方法1。更多的样品在我的口袋里

因此,屏幕外渲染算法包括以下步骤:

  • 创建隐藏窗口,因为您需要窗口,甚至隐藏到 创建OpenGL上下文
  • 创建OpenGL上下文
  • 创建帧缓冲区对象(FBO)
  • 创建渲染缓冲区(颜色和深度)并将其附加到FBO(有关详细信息,请参见)
  • 将FBO绑定到OpenGL上下文以进行渲染
  • 渲染某物。在本例中,我仅使用2D基本体进行简化,但缓冲区已准备好进行深度测试的3D渲染
  • 设置读取缓冲区,在我们的例子中只有一个FBO,因此无需选择一个进行读取
  • 使用glReadPixels()从颜色渲染缓冲区读取渲染数据
  • 对接收到的数据执行任何操作,即从中创建PIL图像并将其保存到文件中。还可以使用双分辨率渲染和调整PIL图像的大小,以实现抗锯齿效果
  • 下面是一个完整的例子

    重要3.1.1 PyOpenGL实现有一个BUG!当您刚刚导入WGL时,glReadPixels()开始崩溃

    ctypes.ArgumentError:参数7::错误类型

    要避免这种情况,请转到packagesdir\OpenGL\raw\WGL\u types.py,找到以下行

    HANDLE = POINTER(None)  # /home/mcfletch/pylive/OpenGL-ctypes/src/wgl.h:60
    # TODO: figure out how to make the handle not appear as a void_p within the code...
    HANDLE.final = True
    
    并将其替换为(当然,对于x64,假设对于x86 UINT32)

    所以有一个例子

    from win32api import *
    from win32con import *
    from win32gui import *
    
    from OpenGL.WGL import *
    from OpenGL.GL import *
    from OpenGL.GLU import *
    from OpenGL.GLUT import *
    
    from PIL import Image
    from PIL import ImageOps
    
    import uuid
    
    # =========================================
    # I left here only necessary constants, it's easy to search for the rest
    
    PFD_TYPE_RGBA =         0
    PFD_MAIN_PLANE =        0
    PFD_DOUBLEBUFFER =      0x00000001
    PFD_DRAW_TO_WINDOW =    0x00000004
    PFD_SUPPORT_OPENGL =    0x00000020
    
    # =========================================
    # OpenGL context creation helpers
    
    def mywglCreateContext(hWnd):
        pfd = PIXELFORMATDESCRIPTOR()
    
        pfd.dwFlags = PFD_DRAW_TO_WINDOW | PFD_SUPPORT_OPENGL
        pfd.iPixelType = PFD_TYPE_RGBA
        pfd.cColorBits = 32
        pfd.cDepthBits = 24
        pfd.iLayerType = PFD_MAIN_PLANE
    
        hdc = GetDC(hWnd)
    
        pixelformat = ChoosePixelFormat(hdc, pfd)
        SetPixelFormat(hdc, pixelformat, pfd)
    
        oglrc = wglCreateContext(hdc)
        wglMakeCurrent(hdc, oglrc)
    
        # check is context created succesfully
        # print "OpenGL version:", glGetString(GL_VERSION)
    
    
    def mywglDeleteContext():
        hrc = wglGetCurrentContext()
        wglMakeCurrent(0, 0)
        if hrc: wglDeleteContext(hrc)
    
    
    # =========================================
    # OpenGL Framebuffer Objects helpers
    
    def myglCreateBuffers(width, height):
    
        fbo = glGenFramebuffers(1)
        color_buf = glGenRenderbuffers(1)
        depth_buf = glGenRenderbuffers(1)
    
        # binds created FBO to context both for read and draw
        glBindFramebuffer(GL_FRAMEBUFFER, fbo)
    
        # bind color render buffer
        glBindRenderbuffer(GL_RENDERBUFFER, color_buf)
        glRenderbufferStorage(GL_RENDERBUFFER, GL_RGBA8, width, height)
        glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, color_buf)
    
        # bind depth render buffer - no need for 2D, but necessary for real 3D rendering
        glBindRenderbuffer(GL_RENDERBUFFER, depth_buf)
        glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT, width, height)
        glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, depth_buf)
    
        return fbo, color_buf, depth_buf, width, height
    
    def myglDeleteBuffers(buffers):
        fbo, color_buf, depth_buf, width, height = buffers
        glBindFramebuffer(GL_FRAMEBUFFER, 0)
        glDeleteRenderbuffers(1, color_buf)
        glDeleteRenderbuffers(1, depth_buf)
        glDeleteFramebuffers(1, fbo)
    
    def myglReadColorBuffer(buffers):
        fbo, color_buf, depth_buf, width, height = buffers
        glPixelStorei(GL_PACK_ALIGNMENT, 1)
        glReadBuffer(GL_COLOR_ATTACHMENT0)
        data = glReadPixels(0, 0, width, height, GL_RGBA, GL_UNSIGNED_BYTE)
        return data, width, height
    
    # =========================================
    # Scene rendering
    
    def renderInit(width, height):
    
        glClearColor(0.5, 0.5, 0.5, 1.0)
        glColor(0.0, 1.0, 0.0)
        gluOrtho2D(-1.0, 1.0, -1.0, 1.0)
        glViewport(0, 0, width, height)
    
    
    def render():
    
        glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)
    
        # draw xy axis with arrows
        glBegin(GL_LINES)
    
        # x
        glVertex2d(-1, 0)
        glVertex2d(1, 0)
        glVertex2d(1, 0)
        glVertex2d(0.95, 0.05)
        glVertex2d(1, 0)
        glVertex2d(0.95, -0.05)
    
        # y
        glVertex2d(0, -1)
        glVertex2d(0, 1)
        glVertex2d(0, 1)
        glVertex2d(0.05, 0.95)
        glVertex2d(0, 1)
        glVertex2d(-0.05, 0.95)
    
        glEnd()
    
        glFlush()
    
    # =========================================
    # Windows stuff and main steps
    
    def main():
    
        # Create window first with Win32 API
    
        hInstance = GetModuleHandle(None)
    
        wndClass = WNDCLASS()
    
        wndClass.lpfnWndProc = DefWindowProc
        wndClass.hInstance = hInstance
        wndClass.hbrBackground = GetStockObject(WHITE_BRUSH)
        wndClass.hCursor = LoadCursor(0, IDC_ARROW)
        wndClass.lpszClassName = str(uuid.uuid4())
        wndClass.style = CS_OWNDC
    
        wndClassAtom = RegisterClass(wndClass)
    
        # don't care about window size, couse we will create independent buffers
        hWnd = CreateWindow(wndClassAtom, '', WS_POPUP, 0, 0, 1, 1, 0, 0, hInstance, None)
    
        # Ok, window created, now we can create OpenGL context
    
        mywglCreateContext(hWnd)
    
        # In OpenGL context create Framebuffer Object (FBO) and attach Color and Depth render buffers to it
    
        width, height = 300, 300
        buffers = myglCreateBuffers(width, height)
    
        # Init our renderer
        renderInit(width, height)
    
        # Now everything is ready for job to be done!
        # Render something and save it to file
    
        render()
    
        data, width, height = myglReadColorBuffer(buffers)
        image = Image.frombytes("RGBA", (width, height), data)
        image = ImageOps.flip(image) # in my case image is flipped top-bottom for some reason
    
        # it's easy to achive antialiasing effect by resizing rendered image
        # don't forget to increase initial rendered image resolution and line thikness for 2D
        #image = image.resize((width/2, height/2), Image.ANTIALIAS)
    
        image.save("fbo.png", "PNG")
    
        # Shutdown everything
        myglDeleteBuffers(buffers)
        mywglDeleteContext()
    
    main()
    

    GLUT隐藏窗口方法简单得多,与平台无关,但它会导致窗口闪烁

    要为Django设置它,也就是说,您可以将渲染器实现为单独的web服务器,该服务器在启动时只会按窗口闪烁一次,然后通过http响应返回渲染图像

    from OpenGL.GL import *
    from OpenGL.GLU import *
    from OpenGL.GLUT import *
    
    from PIL import Image
    from PIL import ImageOps
    
    import sys
    
    width, height = 300, 300
    
    def init():
        glClearColor(0.5, 0.5, 0.5, 1.0)
        glColor(0.0, 1.0, 0.0)
        gluOrtho2D(-1.0, 1.0, -1.0, 1.0)
        glViewport(0, 0, width, height)
    
    def render():
    
        glClear(GL_COLOR_BUFFER_BIT)
    
        # draw xy axis with arrows
        glBegin(GL_LINES)
    
        # x
        glVertex2d(-1, 0)
        glVertex2d(1, 0)
        glVertex2d(1, 0)
        glVertex2d(0.95, 0.05)
        glVertex2d(1, 0)
        glVertex2d(0.95, -0.05)
    
        # y
        glVertex2d(0, -1)
        glVertex2d(0, 1)
        glVertex2d(0, 1)
        glVertex2d(0.05, 0.95)
        glVertex2d(0, 1)
        glVertex2d(-0.05, 0.95)
    
        glEnd()
    
        glFlush()
    
    
    def draw():
        render()
        glutSwapBuffers()
    
    def main():
        glutInit(sys.argv)
    
        glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGB)
        glutInitWindowSize(300, 300)
        glutCreateWindow(b"OpenGL Offscreen")
        glutHideWindow()
    
        init()
        render()
    
        glPixelStorei(GL_PACK_ALIGNMENT, 1)
        data = glReadPixels(0, 0, width, height, GL_RGBA, GL_UNSIGNED_BYTE)
        image = Image.frombytes("RGBA", (width, height), data)
        image = ImageOps.flip(image) # in my case image is flipped top-bottom for some reason
        image.save('glutout.png', 'PNG')
    
        #glutDisplayFunc(draw)
        #glutMainLoop()
    
    main()
    

    除了glutHideWindow(),若需要在服务器上执行此操作,可以使用虚拟显示。 requirements.txt:pyopengl、抱枕、pyvirtualdisplay。 软件包:freeglut3 dev,xvfb

    from pyvirtualdisplay import Display
    # before glutInit create virtual display
    display = Display(visible=0, size=(HEIGHT, WIDTH))
    display.start()
    

    欢迎来到SO!你可能想看一看,以避免未回答的问题或否决投票。你的问题非常广泛,我们在这里重视的一件事是,你为解决你的问题做出并表现出事先的努力。因此,我建议您添加代码,以显示您迄今为止的尝试。如果你还没有尝试过一些东西,那么我们不是来帮你做你的工作的,你可能会被标记为“太宽”,但有足够信息的人可能会看到这一点并引导你前进。因为在2020年它仍然是实际的。我想更新我的答案并加下划线,正确的屏幕外渲染方法是使用(惊喜!)屏幕外OpenGL渲染器,称为OS Mesa(用于屏幕外的OS)。有一个自我记录的环境,它展示了如何安装它(见Dockerfile,可能有点过时,请尝试使用版本)并与PyOpenGL一起使用。
    from pyvirtualdisplay import Display
    # before glutInit create virtual display
    display = Display(visible=0, size=(HEIGHT, WIDTH))
    display.start()