I am new to PYopenGL,
Actually, I’m also not sure if PYopenGL is the right approach for my task.
I have a 3D model in a Wavefront obj file format. I need to take a “printscreen” of the model from a given view. In other words, I would need to render the model and instead of display the model save it as an image (jpg)
My idea was to use PYopenGL for this task. However, googling I could find no suggestion or example how to do this. Therefore, I start to have doubts, if PYopenGL is the right tool for my task.
Did somebody of you already something like this or know an example that I can use to learn about?
Thanks in advance.
Michi
GLUT hidden window method is much simpler, and platform independent, but it leads to window blinking.
To setup it for Django i.e. you can implement renderer as separate web server, which will blinks by window only once on start, and then returning rendered images by http response.
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()
My answer (partially based on CodeSurgeon's answer) is for the second part of the question.
Off-screen rendering (means render something to internal buffer instead of visible window and save rendered image to file or transfer as http response to display on web page) in PyOpenGL (so as in OpenGL itself) is little tricky because everything done by GLUT so far (create window, init opengl context etc) you need to do by hands now, because you don't need the standard GLUT window to pop up or even blink.
So there are 3 methods for off-screen rendering in OpenGL:
1) Use GLUT for initialization, but hide glut window and render to it. This method is totally platform independent, but GLUT window appears for short time during initialization, so it's not so suitable for web server i.e. But you still can setup it as separate web server, which do initialization only on startup and use some interface to communicate with it.
2) Manually create everything: hidden window, OpenGL context, and Framebuffer Object to render to. This method is good, cause you control everything and no window appears, but creation of context is platform specific (below is example for Win64)
3) 3rd method is like method 2, but use default Framebuffer created by WGL, instead of creating FBO by hands. Same effects as method 2, but simpler. If you don't need FBO for some other reason, may be the preferable one.
Now I'll describe method 2, hardcore one. More samples are in my GitHub repository.
So off-screen rendering algorithm consists of following steps:
Create hidden window, because you need the window, even hidden to
create OpenGL context
Create OpenGL context
Create Framebuffer object (FBO)
Create Rendering buffers (Color and Depth) and attach them to FBO (see FBO manual for details)
Bind FBO to OpenGL context for rendering
Render something. In this example I use only 2D primitives for simplification, but buffers are ready for 3D rendering with depth-test
Setup buffers for reading, in our case there is only one FBO, so no need to choose one to read from
Read rendered data from Color render buffer with glReadPixels()
Do whatever you want with received data, i.e. create PIL image from it and save it to file. Also you can render with double resolution and resize PIL image for anti-aliasing effect.
So there is full example below.
Important! 3.1.1 PyOpenGL implementation have a BUG! When you just import WGL, glReadPixels() starts to crash with
ctypes.ArgumentError: argument 7: : wrong type
To avoid this go to your packages dir\OpenGL\raw\WGL_types.py , find the following lines
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
and replace it with (for x64 of course, for x86 UINT32 suppose)
HANDLE = UINT64
HANDLE.final = True
So there is example
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()
In additional to glutHideWindow(), if you need do it on server, you can use virtual display.
requirements.txt: pyopengl, pillow, pyvirtualdisplay.
packages: freeglut3-dev, xvfb.
from pyvirtualdisplay import Display
# before glutInit create virtual display
display = Display(visible=0, size=(HEIGHT, WIDTH))
display.start()
PyOpenGL can be used for your purposes if all you are interested in is rendering your 3d model/scene from a specific angle to an image as long as you do not mind having an OpenGL window open and running.
There are many sources online that discuss how to write parsers for the .obj format. In addition to looking at the wikipedia article on the format here, I believe you can find an implementation of a fixed function obj loader on the pygame website. If you are making the .obj models yourself, it will be easier as the spec is pretty loose and it can be tough to write a robust parser. Alternatively, you can use libraries like Assimp to load your models and extract their data, which has the python pyAssimp bindings.
As for saving a screenshot, you need to render the 3D scene to a texture. I would strongly recommend taking a look at this answer. You would need to learn about how to use glReadPixels as well as how to use FBOs (Frame Buffer Objects) if you want to do offscreen rendering.
I'm trying to do my uni project and I'm using pyglet for the task . This is part of the code that makes me a problem.
from pyglet.gl import *
from pyglet.window import key
from pyglet.window import mouse
window=pyglet.window.Window(resizable=True)
#window.event
def on_draw():
glutInitDisplayMode (GLUT_RGB | GLUT_DOUBLE)
glutInitWindowSize (width, height)
glutInitWindowPosition (100, 100)
glClearColor( 1.0, 1.0, 1.0, 1.0)
glClear (GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)
myObject ()
glutSwapBuffers()
When i searched for functions glutInitDisplayMode, glutInitWindowSize and glutInitWindowPosition it only shows pyOpenGL threads, so do they exist for pyglet or im just defining them wrong?
Terminal Output:
glutInitDisplayMode (GLUT_RGB | GLUT_DOUBLE)
NameError: global name 'glutInitDisplayMode' is not defined
and same is for other two
So, glutInitDisplayMode is a GL function but as far as I know, it's not made avilable by Pyglet because it's not really needed.
Now, these are some what speculations and correct me if I'm wrong.
But calling the following will set up the context for you:
pyglet.window.Window(...)
There for all these are unnecessary:
glutInitDisplayMode (GLUT_RGB | GLUT_DOUBLE)
glutInitWindowSize (width, height)
glutInitWindowPosition (100, 100)
Instead what you want to do is:
window = pyglet.window.Window(width=800, height=600)
window.set_location(100, 100)
There's also the option to create a specific config and context and inject:
config = pyglet.gl.Config(double_buffer=True)
context = context = config.create_context(shared_context)
window = pyglet.window.Window(config=config, context=context)
Hope this clarify anything for you.
I would like to open and close a glut window several times in my program. I came up with this python code:
#!/usr/bin/python
from OpenGL.GLUT import *
from OpenGL.GLU import *
from OpenGL.GL import *
#parameters for viewer
name = 'Viewer'
width, height = 500, 500
spheresize = 0.3
def display():
glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT)
glLoadIdentity()
gluLookAt(0,0,10,0,0,0,0,1,0)
glPushMatrix()
color = [1.0,0.0,0.0,1.0]
glMaterialfv(GL_FRONT,GL_DIFFUSE,color)
glTranslatef(0.0,0.0,1.0)
glutSolidSphere(spheresize,20,20)
glPopMatrix()
glutSwapBuffers()
return
def closing():
glutSetOption(GLUT_ACTION_ON_WINDOW_CLOSE,GLUT_ACTION_GLUTMAINLOOP_RETURNS) glutLeaveMainLoop()
return
#---------------BEGIN PROGRAM----------------
while 1:
glutInit()
glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGB | GLUT_DEPTH)
glutInitWindowSize(width,height)
glutCreateWindow(name)
glClearColor(0.,1.0,0.,1.)
glShadeModel(GL_SMOOTH)
glEnable(GL_CULL_FACE)
glEnable(GL_DEPTH_TEST)
glEnable(GL_LIGHTING)
glEnable(GL_LIGHT0)
glMatrixMode(GL_PROJECTION)
gluPerspective(40.,1.,1.,40.)
glMatrixMode(GL_MODELVIEW)
glPushMatrix()
glutDisplayFunc(display)
glutCloseFunc(closing)
glutMainLoop()
print "\n\nMAIN MENUE:"
print "p - print"
print "q - quit"
option = raw_input()
if option == "q":
raise SystemExit
elif option == "p":
print ("Hello World!")
When I try to run this, the window closes as I intended, but it crashes after using option 'p' with
freeglut ERROR: Function glutCreateWindow called without first calling 'glutInit'.
After I found some other posts about a similar problem, I assumed it might be a problem of my library linkage, so I tried to run it in a clean python environment on another computer. It gave the same error.
Does anyone have an idea what might be my mistake?
I'm using Python and OpenGL to make some calculations related to a physical system and then display them and be able to manipulate the image rotating it, translating it, ...
I'm not a professional programmer and I have little experience using OpenGL and I'm having some issues trying to get what I want. I would like to be able to save the window that I get as an output in a video file and to do so I've seen the possibility of using glReadPixels to capture each frame and then put them all together.
I am using right now something that looks like the following code and I want to be able to save the frames to images but, although I've been able to save to save the pixels to an array using glReadPixels, I don't know where nor how to call the function that I've defined as captureScreen. What should I do to get the output that I'm looking for?
from OpenGL.GL import *
from OpenGL.GLU import *
from OpenGL.GLUT import *
from PIL import Image
from scipy import *
import numpy as np
import random as rnd
def captureScreen(file_):
glPixelStorei(GL_PACK_ALIGNMENT, 1)
data = glReadPixels(0, 0, 800, 800, GL_RGBA, GL_UNSIGNED_BYTE)
image = Image.fromstring("RGBA", (800, 800), data)
image.save(file_, 'png')
def main():
glutInit(sys.argv)
glutInitDisplayMode (GLUT_DOUBLE | GLUT_RGB | GLUT_DEPTH)
glutInitWindowPosition(0, 0)
glutInitWindowSize(800, 800)
glutCreateWindow ("Test")
glEnable(GL_DEPTH_TEST)
glDepthMask(GL_TRUE)
glEnable(GL_BLEND)
glEnable(GL_TEXTURE_2D)
glEnable(GL_COLOR_MATERIAL)
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)
glPointSize( 6.0 )
glLineWidth( 2.0 )
glEnable(GL_POINT_SMOOTH)
glEnable(GL_LINE_SMOOTH)
glHint(GL_LINE_SMOOTH_HINT, GL_NICEST)
glEnable(GL_POLYGON_SMOOTH)
glHint(GL_POLYGON_SMOOTH_HINT, GL_NICEST)
glEnableClientState(GL_VERTEX_ARRAY)
glEnableClientState(GL_COLOR_ARRAY)
glClearColor(0.0, 0.0, 0.0, 0.0)
glutFullScreen()
glutKeyboardFunc(keyboard)
glutIdleFunc(dynamics)
glutDisplayFunc(display)
glutMouseFunc(mouse)
glutMainLoop()
if __name__ == '__main__':
main()
keyboard, display, dynamics and mouse are functions previously defined.
You can call glReadPixels() just after glutSwapBuffers():
glutSwapBuffers()
glReadBuffer(GL_FRONT)
glReadPixels(...)
Or just before:
glReadBuffer(GL_BACK)
glReadPixels(...)
glutSwapBuffers()
So captureScreen() should be called from your "display" function which is probably the one which calls glutSwapBuffers()
I have the example code below, simplified from a larger project. I have been trying to make alpha channels work i have enabled blending and written a similar example using pygame which works. How ever setting up opengl to work with a glx context seems to stop blending from working, i have a feeling i need to enabel some parameter to the gl context setup but have not been able to find out what that parameter is.
Any suggestions on why this is not working, i have tried on two different machines one with high end radeon and another with an intel graphics card both do the same however.
#blending is not working any ideas why ?
import sys
from OpenGL.GL import *
from OpenGL.GLU import *
from OpenGL import GLX
from OpenGL.raw._GLX import struct__XDisplay
from OpenGL import GL
from ctypes import *
import Xlib
from Xlib.display import Display
from gi.repository import Gtk, GdkX11, Gdk
class gtkgl:
""" these method do not seem to exist in python x11 library lets exploit the c methods """
xlib = cdll.LoadLibrary('libX11.so')
xlib.XOpenDisplay.argtypes = [c_char_p]
xlib.XOpenDisplay.restype = POINTER(struct__XDisplay)
xdisplay = xlib.XOpenDisplay("")
display = Xlib.display.Display()
attrs = []
xwindow_id = None
width = height = 200
def __init__(self):
""" lets setup are opengl settings and create the context for our window """
self.add_attribute(GLX.GLX_RGBA, True)
self.add_attribute(GLX.GLX_RED_SIZE, 1)
self.add_attribute(GLX.GLX_GREEN_SIZE, 1)
self.add_attribute(GLX.GLX_BLUE_SIZE, 1)
self.add_attribute(GLX.GLX_ALPHA_SIZE, 1)
self.add_attribute(GLX.GLX_DOUBLEBUFFER, 0)
glClearDepth(1.0)
glEnable(GL_DEPTH_TEST)
glEnable(GL_BLEND)
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)
glShadeModel(GL_SMOOTH)
glDepthFunc(GL_LEQUAL)
glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST)
glEnable(GL_COLOR_MATERIAL)
glEnable(GL_LIGHTING)
glEnable(GL_LIGHT0)
glLight(GL_LIGHT0, GL_POSITION, (0, 1, 1, 0))
xvinfo = GLX.glXChooseVisual(self.xdisplay, self.display.get_default_screen(), self.get_attributes())
configs = GLX.glXChooseFBConfig(self.xdisplay, 0, None, byref(c_int()))
self.context = GLX.glXCreateContext(self.xdisplay, xvinfo, None, True)
def add_attribute(self, setting, value):
"""just to nicely add opengl parameters"""
self.attrs.append(setting)
self.attrs.append(value)
def get_attributes(self):
""" return our parameters in the expected structure"""
attrs = self.attrs + [0, 0]
return (c_int * len(attrs))(*attrs)
def configure(self, wid):
""" """
self.xwindow_id = GdkX11.X11Window.get_xid(wid)
if(not GLX.glXMakeCurrent(self.xdisplay, self.xwindow_id, self.context)):
print 'failed'
glViewport(0, 0, self.width, self.height)
def draw_start(self):
"""make cairo context current for drawing"""
if(not GLX.glXMakeCurrent(self.xdisplay, self.xwindow_id, self.context)):
print "failed"
def draw_finish(self):
"""swap buffer when we have finished drawing"""
GLX.glXSwapBuffers(self.xdisplay, self.xwindow_id)
def test(self):
"""Test method to draw something so we can make sure opengl is working and we can see something"""
self.draw_start()
glClearColor(0.0, 0.0, 0.0, 0.0)
glClear(GL_COLOR_BUFFER_BIT)
glBegin(GL_TRIANGLES)
glIndexi(0)
glColor4f(1.0, 0.0, 0.0, 0.2)
glVertex2i(0, 1)
glIndexi(0)
glColor4f(0.0, 1.0, 0.0, 0.2)
glVertex2i(-1, -1)
glIndexi(0)
glColor4f(0.0, 0.0, 1.0, 1.0)
glVertex2i(1, -1)
glEnd()
self.draw_finish()
class gui():
glwrap = gtkgl()
def __init__(self):
self.window = Gtk.Window()
self.window.realize()
self.window.resize(self.glwrap.width, self.glwrap.height)
self.window.set_resizable(True)
self.window.set_reallocate_redraws(True)
self.window.set_title("GTK3 with opengl")
self.window.connect('delete_event', Gtk.main_quit)
self.window.connect('destroy', lambda quit: Gtk.main_quit())
self.drawing_area = Gtk.DrawingArea()
self.drawing_area.connect('configure_event', self.on_configure_event)
self.drawing_area.connect('draw', self.on_draw)
self.drawing_area.set_double_buffered(False)
self.drawing_area.set_size_request(self.glwrap.width, self.glwrap.height)
self.window.add(self.drawing_area)
self.window.show_all()
def on_configure_event(self, widget, event):
self.glwrap.configure(widget.get_window())
return True
def on_draw(self, widget, context):
self.glwrap.test()
def main():
g = gui()
Gtk.main()
if __name__ == '__main__':
main()
It's not the OpenGL context missing something (at the point of creation I mean). You're simply not enabling blending. You need to add a glEnable(GL_BLEND); before drawing primitives that require blending.
BTW: Using 1 bit as minimum requirement for RGB channels can yield to undesired results. Use at least 5 bits (for 16 bit displays) or better yet 8 bits. Also you don't need an alpha channel on the main framebuffer for blending to work. So unless you want to create transparent windows on a compositor, then you should use 0 alpha bits on the window's framebuffer.