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.
Related
I'm writing an application with pyglet where all rendered objects are children of the window class. I'm trying to draw a simple rectangle, but using on_resize seems to break everything. There's no error message, it just doesn't draw the rectangle.
This is my file structure:
main.py
lib
|-- window.py
|-- quad.py
This code doesn't work, but if I remove the on_resize methods it does:
-------------
FILE: main.py
-------------
import pyglet
import lib
window = lib.window.Window(1280, 720)
window.add_object(lib.quad.Quad())
pyglet.app.run()
-------------------
FILE: lib/window.py
-------------------
import pyglet
from . import quad
class Window(pyglet.window.Window):
def __init__(self, w, h):
super().__init__(width=w, height=h)
self.objects = []
def on_draw(self):
for obj in self.objects:
obj.on_draw()
def on_resize(self, width, height):
for obj in self.objects:
obj.on_resize(width, height)
def add_object(self, obj):
self.objects.extend([obj])
-------------
FILE: lib/quad.py
-------------
import pyglet
import pyglet.gl
class Quad():
def __init__(self):
self.quad = pyglet.graphics.vertex_list(4, ('v2i', (10, 10, 100, 10, 100, 100, 10, 100)), ('c3B', (0, 0, 255, 0, 0, 255, 0, 255, 0, 0, 255, 0)))
def on_draw(self):
self.quad.draw(pyglet.gl.GL_QUADS)
def on_resize(self, width, height):
pass
I would like to be able to keep rendered objects as children of the window class, since that makes running event handlers much easier. Is there any way that I can make this work with the on_resize handler? Any help is appreciated.
EDIT: I tried removing on_resize from the Quad class, and making on_resize do nothing in the Window class. It seems like the existence of the on_resize function is the issue--if on_resize exists, pyglet wont draw the rectangle.
The problem is by subclassing Window, (MyWindow in this case), when you define an on_resize() event handler, you are replacing the inherited handler with your own. What you want to do instead is to push your on_resize() handler onto the event handling stack so that both handlers get called. You want to do the same some for of the others like on_close(). Example:
class MyWindow(pyglet.window.Window):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.set_minimum_size(400, 300)
#
# Push your handler onto the on_resize handler stack
#
self.push_handlers(on_resize=self.local_on_resize)
def local_on_resize(self, x, y):
# Do stuff
def on_draw(self):
self.clear()
self.triangle.draw()
Note that you can reproduce the failure by having your local_on_resize() handler return a value of pyglet.event.EVENT_HANDLED, (which will stop processing of the remaining handlers), and the graphics won't render.
In the on_resize method add the glViewport function call. And also don't forget to set up an orthogonal projection. Take a look at my code, it draws a triangle at the center of the screen.
from pyglet.gl import *
class Triangle:
def __init__(self):
self.vertices = pyglet.graphics.vertex_list(3, ('v3f', [0,0,0, 300,0,0, 150,300,0]),
('c3B', [255,0,0, 0,255,0, 0,0,255]))
def draw(self):
self.vertices.draw(GL_TRIANGLES)
class MyWindow(pyglet.window.Window):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.set_minimum_size(400, 300)
glClearColor(0.2, 0.25, 0.2, 1.0)
glOrtho(0, 1280, 0, 720, -10, 10) # setup orthogonal projection
self.triangle = Triangle()
def on_draw(self):
self.clear()
glPushMatrix()
glTranslatef(640-150 ,360-150, 0) # translate the Triangle to the center
self.triangle.draw()
glPopMatrix()
def on_resize(self, width, height):
glViewport(0, 0, width, height) # resize the viewport
if __name__ == "__main__":
MyWindow(1280, 720, 'My window', resizable=True)
pyglet.app.run()
from OpenGL.extensions import alternate
from OpenGL.GL import *
from OpenGL.GL.ARB.multitexture import *
from OpenGL.GLU import *
from OpenGL.GLUT import *
class TestTexture():
def __init__(self):
self.window_width = 800
self.window_height = 800
def init(self):
glClearColor(0.0, 0.0, 0.0, 0.0)
glMatrixMode(GL_PROJECTION)
glLoadIdentity()
glOrtho(-1.0, 1.0, -1.0, 1.0, -1.0, 1.0)
glMatrixMode(GL_MODELVIEW)
glLoadIdentity()
glEnable(GL_TEXTURE_2D)
def display(self):
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)
glLoadIdentity()
glBegin(GL_TRIANGLES)
glVertex3f(-1.0, 0.0, 0.0)
glVertex3f(1.0, 0.0, 0.0)
glVertex3f(0.0, 1.0, 0.0)
glEnd()
glFlush()
glutSwapBuffers()
def reshape(self, w, h):
self.window_width = w
self.window_height = h
glViewport(0, 0, self.window_width, self.window_height)
def animate(self):
glutPostRedisplay()
def visible(self, vis):
if (vis == GLUT_VISIBLE):
glutIdleFunc(self.animate)
else:
glutIdleFunc(0)
def key_pressed(self, *args):
if args[0] == b"\x1b":
sys.exit()
def run(self):
glutInit(sys.argv)
glutInitDisplayMode(GLUT_RGBA | GLUT_DOUBLE | GLUT_ALPHA | GLUT_DEPTH)
glutInitWindowSize(self.window_width, self.window_height)
glutInitWindowPosition(800, 100)
glutCreateWindow(b'MCVE')
glutDisplayFunc(self.display)
glutReshapeFunc(self.reshape)
glutIdleFunc(self.animate)
glutVisibilityFunc(self.visible)
glutKeyboardFunc(self.key_pressed)
self.init()
glutMainLoop()
if __name__ == "__main__":
TestTexture().run()
I've already tried few things in order to resize&refresh properly this window but no luck so far. When the window is resized the scene should be rendered properly on real-time but it's not, instead the scene is updated only when you release the mouse (reshape function is not called) or maybe when it's being resized it gets a little chance to update but the final result is definitely not cool at all.
Worth mentioning that a pyqt opengl app is resizing ok and the show window contents while dragging option is enabled.
The test has been made on windows7, PyOpenGL==3.1.1 and python3.5.1_x86
So the question would be, how can I resize&refresh a glut window properly on realtime?
First things first glViewport does not belong into the reshape handler (if I had a cent for every time I wrote this…).
The problem you're running into is not something you can easily address from the "outside" of GLUT because it essentially boils down to how the innards of the main loop are implemented. For smooth updates of the window's contents during resize technically the main loop has to accommodate for this situation, by smoothly interleaving resize events with calls of the display function. The way glutPostRedisplay flags the main loop for redraw will call the display function every few resize steps, but it may accompanied by flicker and jerky redraws.
Your best bet is to do something that's normally frowned upon: Call the display function (including buffer swap) at the end of the resize handler and do not call glutPostRedisplay. However this might be still prone to flicker, depending on how WSI background erasure is implemented. The problem with that is, that resize events may queue up and resize steps long begone show up lagging behind user action.
For truly smooth resize updates an appropriately written main loop is required, that coalesces input/resize events to avoid queue-lag issues and allows for on-resize drawing operation without automatic background erasure to support smooth updates. Current implementations of GLUT don't do this.
Today I am trying to develop an UI using cefpython which allows me to embed a web browser and interacts with it with javascript bindings.
I'm using it to develop on Windows platform.
For this purpose, I am using the "multi_threaded_message_loop" flag which allows me to gain in performance.
I'm also using wxpython on python 3 to embed it.
The problem is when I resize my window, the use of WindowUtils.OnSize() freezes my app. 99% of the time, it happens when the browser is loading (but it also happens when it's done (rarely)).
Here is a sample code to reproduce :
import platform
import sys
import wx
from cefpython3 import cefpython
WindowUtils = cefpython.WindowUtils()
WIDTH = 800
HEIGHT = 600
import os
class MainFrame(wx.Frame):
browser = None
mainPanel = None
def createMainBrowser(self):
self.browser = self.createBrowser(self.mainPanel)
def createBrowser(self, parent):
browser = cefpython.CreateBrowserSync(
self.getWindowInfo(parent),
browserSettings={},
navigateUrl='http://www.google.com'
)
return browser
def getWindowInfo(self, parent):
windowInfo = cefpython.WindowInfo()
windowInfo.SetAsChild(parent.GetHandle(), [0, 0, WIDTH, HEIGHT])
return windowInfo
def __init__(self):
wx.Frame.__init__(
self, parent=None, id=wx.ID_ANY, title='wx', size=(WIDTH, HEIGHT)
)
self.mainPanel = wx.Panel(self)
self.mainPanel.SetBackgroundColour(wx.GREEN)
cefpython.PostTask(cefpython.TID_UI, self.createMainBrowser)
self.mainPanel.Bind(wx.EVT_SIZE, self.OnSize)
def OnSize(self, _):
if not self.browser:
return
WindowUtils.OnSize(self.mainPanel.GetHandle(), 0, 0, 0)
self.browser.NotifyMoveOrResizeStarted()
class App(wx.App):
def OnInit(self):
frame = MainFrame()
frame.Show()
return True
if __name__ == '__main__':
sys.excepthook = cefpython.ExceptHook # To shutdown all CEF processes on error
cefpython.Initialize({
"locales_dir_path": cefpython.GetModuleDirectory() + "/locales",
"browser_subprocess_path": cefpython.GetModuleDirectory() + "/subprocess",
"auto_zooming": "system_dpi",
"multi_threaded_message_loop": True,
})
app = App(False)
app.MainLoop()
cefpython.Shutdown()
Thank you a lot for your help !
Alann
Problem solved !
Instead of using
def OnSize(self, _):
if not self.browser:
return
WindowUtils.OnSize(self.mainPanel.GetHandle(), 0, 0, 0)
self.browser.NotifyMoveOrResizeStarted()
I use
def OnSize(self, sizeEvent):
if not self.browser:
return
w = sizeEvent.GetSize().GetWidth()
h = sizeEvent.GetSize().GetHeight()
win32gui.SetWindowPos(self.browser.GetWindowHandle(), 0, 0, 0, w, h, 0)
self.browser.NotifyMoveOrResizeStarted()
I don't know if this is because I'm on windows 10 but maybe WindowsUtils needs to be updated !
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 have a basic OpenGL square drawing code that I want to put another thread using Python. This is the normal code which is working well,
from OpenGL.GLUT import *
from OpenGL.GL import *
from OpenGL.GLU import *
class Drawer:
def drawQuad(self, x,y,width):
halfW=width/2
glBegin(GL_QUADS)
glVertex3f(x-halfW, y-halfW, 0)
glVertex3f(x-halfW, y+halfW, 0)
glVertex3f(x+halfW, y+halfW, 0)
glVertex3f(x+halfW, y-halfW, 0)
glEnd()
def display(self):
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)
glMatrixMode(GL_MODELVIEW)
glLoadIdentity()
glColor3f(0, 0, 1)
self.drawQuad(0.0,0.0,0.6)
glutSwapBuffers( )
class Visualizer:
drawer = Drawer()
def __init__(self):
glutInit(sys.argv)
glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGB)
glutInitWindowSize(250, 250)
glutInitWindowPosition(100, 100)
glutCreateWindow(sys.argv[0])
glutDisplayFunc( self.drawer.display )
glClearColor ( 0, 0, 0, 0 )
glShadeModel( GL_SMOOTH )
glutMainLoop()
visualizer = Visualizer()
This works on my machine flawlessly. Now, I want to put this on a thread. I did the following,
class Thread(threading.Thread):
visualizer = None
def run(self):
self.visualizer = Visualizer()
thread = Thread()
thread.start()
When I run the thread, instead of visualizer itself, it does not work. The OpenGL window does not appear. I cannot see what the problem is.
I tried the threading with some other examples, they seemed working well. I am confused a bit. What is the problem with my threading approach for OpenGL?