如何将 PYopenGL 写入 JPG 图像

mor*_*sen -1 python image pyopengl wavefront

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

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

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

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

提前致谢。

Ant*_*kov 7

GLUT 隐藏窗口方法更简单,并且与平台无关,但会导致窗口闪烁。

要为 Django ie 设置它,您可以将渲染器实现为单独的 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()
Run Code Online (Sandbox Code Playgroud)


Ant*_*kov 5

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

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

所以OpenGL中离屏渲染有3种方法:

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

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

3)第三种方法与方法2类似,但使用WGL创建的默认Framebuffer,而不是手动创建FBO。与方法2效果相同,但更简单。如果您因其他原因不需要 FBO,那么 FBO 可能是更好的选择。

现在我将描述方法 2,即硬核方法。更多示例位于我的GitHub 存储库中。

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

  1. 创建隐藏窗口,因为你需要窗口,甚至隐藏来创建OpenGL上下文
  2. 创建 OpenGL 上下文
  3. 创建帧缓冲区对象 (FBO)
  4. 创建渲染缓冲区(颜色和深度)并将它们附加到 FBO(有关详细信息,请参阅FBO 手册
  5. 将FBO绑定到OpenGL上下文进行渲染
  6. 渲染一些东西。在此示例中,我仅使用 2D 图元进行简化,但缓冲区已准备好通过深度测试进行 3D 渲染
  7. 设置读取缓冲区,在我们的例子中只有一个 FBO,因此不需要选择一个来读取
  8. 使用 glReadPixels() 从颜色渲染缓冲区读取渲染数据
  9. 对收到的数据执行任何您想要的操作,即从中创建 PIL 图像并将其保存到文件中。您还可以使用双分辨率渲染并调整 PIL 图像的大小以获得抗锯齿效果。

下面有完整的例子。

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

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

为了避免这种情况,请转到您的包 dir\OpenGL\raw\WGL_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
Run Code Online (Sandbox Code Playgroud)

并将其替换为(当然对于 x64,假设对于 x86 UINT32)

HANDLE = UINT64
HANDLE.final = True
Run Code Online (Sandbox Code Playgroud)

所以有例子

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()
Run Code Online (Sandbox Code Playgroud)