如何将PYopenGL写入JPG-imag

2024-05-29 02:00:11 发布

您现在位置:Python中文网/ 问答频道 /正文

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

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

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

你们中有人已经有类似的事情了吗?或者知道一个我可以用来学习的例子吗?在

提前谢谢。在

米奇


Tags: 模型图像视图obj屏幕事情建议例子
3条回答

我的答案(部分基于CodeSurgeon's answer)是针对问题的第二部分。在

屏幕外渲染(意味着将某些内容呈现到内部缓冲区而不是可见窗口,并将渲染图像保存到文件中或作为http响应传输到网页上显示)在PyOpenGL中(就像在OpenGL中一样)并不复杂,因为到目前为止,GLUT所做的一切(创建窗口,init opengl context等等)你现在需要手动操作,因为你不需要标准的GLUT窗口来弹出甚至闪烁。在

因此,在OpenGL中有3种离屏渲染方法:

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

2)手动创建所有内容:隐藏窗口、OpenGL上下文和要渲染到的帧缓冲区对象。这个方法很好,因为你可以控制一切,而且没有窗口出现,但是上下文的创建是特定于平台的(下面是Win64的例子)

但默认情况下,创建第三帧缓冲区的方法是fgl,而不是使用第三帧方法。效果与方法2相同,但更简单。如果你不需要FBO的其他原因,可能是更好的一个。在

现在我将描述方法2,核心方法之一。更多的样品在我的GitHub repository。在

因此离屏渲染算法包括以下步骤:

  1. 创建隐藏窗口,因为你需要窗口,甚至隐藏到 创建OpenGL上下文
  2. 创建OpenGL上下文
  3. 创建帧缓冲区对象(FBO)
  4. 创建渲染缓冲区(颜色和深度)并将它们附加到FBO(有关详细信息,请参见FBO manual
  5. 将FBO绑定到OpenGL上下文以进行渲染
  6. 渲染某物。在这个例子中,我只使用二维基本体来简化,但是缓冲区已经准备好进行深度测试的三维渲染
  7. 设置读取缓冲区,在我们的例子中只有一个FBO,所以不需要选择一个来读取
  8. 使用glReadPixels()从颜色渲染缓冲区读取渲染数据
  9. 对接收到的数据做任何你想做的事情,即从中创建PIL图像并保存到文件中。也可以用双分辨率渲染和调整PIL图像大小以获得抗锯齿效果。在

下面是完整的例子。在

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

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

要避免这种情况,请转到您的包dir\OpenGL\raw\WGL_类型.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,假设对于x86uint32)

^{pr2}$

所以有个例子

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

如果您感兴趣的只是从特定角度渲染三维模型/场景到图像,那么PyOpenGL可以用于您的目的,只要您不介意打开并运行OpenGL窗口。在

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

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

相关问题 更多 >

    热门问题