OpenGL render view without a visible window in python - python

I need to render some scene. I managed to do it in python using pyopengl and pygame. The problem is that it creates a window for a short period of time.
I want to render the same image and save it, without creating a visible window (Or possibly without creating a window at all, and even without pygame).
import cv2
import numpy as np
import pygame
from pygame.locals import *
from OpenGL.GL import *
from OpenGL.GLU import *
def main():
DISPLAY_WIDTH = 900
DISPLAY_HEIGHT = 900
pygame.init()
pygame.display.set_mode((DISPLAY_WIDTH, DISPLAY_HEIGHT), DOUBLEBUF | OPENGL)
gluPerspective(90, (DISPLAY_WIDTH / DISPLAY_HEIGHT), 0.01, 12)
glEnable(GL_TEXTURE_2D)
glEnable(GL_DEPTH_TEST)
glDepthFunc(GL_LEQUAL)
glRotatef(-90, 1, 0, 0) # Straight rotation
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)
glRotatef(285, 0, 0, 1) # Rotate yaw
glTranslatef(-5, -3, -2) # Move to position
# Draw rectangle
glBegin(GL_QUADS)
glColor3f(1, 0, 0)
glVertex3f(2, 2, 0)
glVertex3f(2, 2, 2)
glVertex3f(2, 6, 2)
glVertex3f(2, 6, 0)
glEnd()
image_buffer = glReadPixels(0, 0, DISPLAY_WIDTH, DISPLAY_HEIGHT, OpenGL.GL.GL_RGB, OpenGL.GL.GL_UNSIGNED_BYTE)
image = np.frombuffer(image_buffer, dtype=np.uint8).reshape(DISPLAY_WIDTH, DISPLAY_HEIGHT, 3)
cv2.imwrite(r"C:\temp\image.png", image)
pygame.quit()
if __name__ == "__main__":
main()

I want to render the same image and save it, without creating a visible window
It is not possible to create an OpenGL Context with an version above 1.0 without any window.
See the answer to the question Creating OpenGL context without window.
But it is possible to use a completely hidden window for "offscreen" rendering.
Sadly it is not possible to create a initially hidden window with Pygame.
It is only possible to hide a window after it was created by pygame.display.iconify().
See also Hiding pygame display.
But it is possible to create a initially hidden window with the GLFW library by setting the window hint VISIBLE to False.
The glfw library can be found at glfw 1.7.0.
The code may look like this:
import cv2
import numpy as np
from OpenGL.GL import *
from OpenGL.GLU import *
import glfw
def main():
DISPLAY_WIDTH = 900
DISPLAY_HEIGHT = 900
# Initialize the library
if not glfw.init():
return
# Set window hint NOT visible
glfw.window_hint(glfw.VISIBLE, False)
# Create a windowed mode window and its OpenGL context
window = glfw.create_window(DISPLAY_WIDTH, DISPLAY_HEIGHT, "hidden window", None, None)
if not window:
glfw.terminate()
return
# Make the window's context current
glfw.make_context_current(window)
gluPerspective(90, (DISPLAY_WIDTH / DISPLAY_HEIGHT), 0.01, 12)
glEnable(GL_TEXTURE_2D)
glEnable(GL_DEPTH_TEST)
glDepthFunc(GL_LEQUAL)
glRotatef(-90, 1, 0, 0) # Straight rotation
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)
glRotatef(285, 0, 0, 1) # Rotate yaw
glTranslatef(-5, -3, -2) # Move to position
# Draw rectangle
glBegin(GL_QUADS)
glColor3f(1, 0, 0)
glVertex3f(2, 2, 0)
glVertex3f(2, 2, 2)
glVertex3f(2, 6, 2)
glVertex3f(2, 6, 0)
glEnd()
image_buffer = glReadPixels(0, 0, DISPLAY_WIDTH, DISPLAY_HEIGHT, OpenGL.GL.GL_RGB, OpenGL.GL.GL_UNSIGNED_BYTE)
image = np.frombuffer(image_buffer, dtype=np.uint8).reshape(DISPLAY_WIDTH, DISPLAY_HEIGHT, 3)
cv2.imwrite(r"C:\temp\image.png", image)
glfw.destroy_window(window)
glfw.terminate()
if __name__ == "__main__":
main()

Related

Pyglet render into texture

I'm writing a drawing program using pyglet, and I want to be able to have the image being created as separate from the window's buffer (for instance, the image could be larger than the window, or may want to draw to this image at a different rate than the main window is being re-drawn). I want to be able to draw into this off-screen image, then display it in the window, but pyglet doesn't allow drawing to anything else than a window. Is there any simple way I can do this?
I've tried creating a second hidden pyglet window, but this gets rendered at the same rate as the main window which I definitely don't want.
The closest I found was Pyglet draw text into texture, but the code there isn't complete, and also no longer works as the opengl version used by pyglet has moved on.
The following code works for me, perhaps someone else can improve on my answer:
import pyglet
from pyglet.gl import *
from ctypes import byref
W, H = 800, 600
image = pyglet.image.create(W, H)
texture = image.get_texture()
window = pyglet.window.Window(width=W, height=H)
fbo_id = gl.GLuint(0)
glGenFramebuffers(1, byref(fbo_id))
glBindFramebuffer(GL_FRAMEBUFFER, fbo_id)
glBindTexture(GL_TEXTURE_2D, texture.id)
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, texture.id, 0)
rect1 = pyglet.shapes.Rectangle(0, 0, W, H, (255, 0, 0) )
rect2 = pyglet.shapes.Rectangle(W//4, H//4, W//2, H//2, (0, 0, 255) )
label = pyglet.text.Label("Hello World", font_name="Times New Roman", font_size=36,
x=W//2, y=H//2, anchor_x="center", anchor_y="center")
rect1.draw()
rect2.draw()
label.draw()
#window.event
def on_mouse_drag(x, y, dx, dy, xxx, modifiers):
glBindFramebuffer(GL_FRAMEBUFFER, fbo_id)
line = pyglet.shapes.Line(x-dx, y-dy, x, y, 3, (0, 255, 255))
line.draw()
#window.event
def on_draw():
glBindFramebuffer(GL_FRAMEBUFFER, 0)
window.clear()
texture.blit(0, 0)
pyglet.app.run()

Strange "X" on PyOpenGL + PyGame

I'm trying to create a PyOpenGL "Hello World" project. I made some code that generates a cube to me and displays it on the screen, the problem is it doesn't show a cube, it show a strange flat 2D "X". I am almost sure that the problem is with my PyGame settings because I've tried to display a simple line and it, actually, nothing was displayed.
I don't know if this is a useful information, but my operational system is Linux - Pop!_os.
FULL CODE: https://github.com/caastilho/PyOpenGL-Problem
LAUNCHER.PY
All the PyGame display settings are storage here
import pygame
from pygame import event as events
from pygame import display
import sys, os
from screeninfo import get_monitors
from canvas import setup, draw
# Root class: "Launcher"
class Launcher:
def __init__(self):
pygame.init()
# Create default settings
self.title = "PyGame Screen"
self.size = (500, 500)
self.flags = 0
setup(self)
self.__setupSurface()
# Run screen
self.__runSurface()
# Setup environment
def __setupSurface(self):
"""Setup environment."""
# Get the main monitor dimensions
for monitor in get_monitors():
if monitor.x == monitor.y == 0:
middle_x = (monitor.width - self.size[0]) // 2
middle_y = (monitor.height - self.size[1]) // 2
offset = f"{middle_x},{middle_y}"
# Setup window
os.environ["SDL_VIDEO_WINDOW_POS"] = offset
display.set_caption(self.title)
self.surface = display.set_mode(self.size, self.flags)
# Run environment
def __runSurface(self):
"""Run environment."""
while True:
draw(self)
pygame.time.wait(10)
# Close condition
for event in events.get():
if event.type == pygame.QUIT:
pygame.quit()
sys.exit()
# Run launcher
if __name__ == "__main__":
Launcher()
CANVAS.PY
All the draw functions are storage here
from pygame import display
from pygame.locals import *
from OpenGL.GL import *
from OpenGL.GLU import *
from Shapes.shape import OpenGL_Cube # Imports the cube class
cube = None
clear_flags = None
# Setup canvas
def setup(app):
global cube, clear_flags
app.size = (1280, 720) # Setup window size
app.title = "PyOpenGl" # Setup window title
app.flags = DOUBLEBUF|OPENGL # Setup PyGame flags
# Setup OpenGL environment
clear_flags = GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT
gluPerspective(45, (app.size[0]/app.size[1]), 0.1, 50)
glTranslatef(0, 0, -5)
glRotatef(0, 0, 0, 0)
# Setup objects
cube = OpenGL_Cube()
# Run canvas
def draw(app):
cube.draw()
display.flip()
glClear(clear_flags)
OUTPUT
EDIT
Just to clarify, the lines of code that i am using to display the cube edges are this one:
glBegin(GL_LINES)
for node in nodes: # Where node = (x, y, z)
glVertex3fv(node)
glEnd
The perspective projection and the model view matrix is never set, because you invoke the function setup before self.__setupSurface().
Note, for any OpenGL instruction a valid and current OpenGL Context is requried, else the instruction has no effect. The OpenGL Context is created when the pygame display surface is generated (display.set_mode()). Hence setup has to be invoked after self.__setupSurface():
class Launcher:
# [...]
def __init__(self):
pygame.init()
# [...]
self.size = (1280, 720)
self.title = "Marching Cubes"
self.flags = DOUBLEBUF|OPENGL
# create OpenGL window (and make OpenGL context current)
self.__setupSurface()
# setup projection and view
setup(self)
# Setup canvas
def setup(app):
global cube, clear_flags
# Setup OpenGL environment
clear_flags = GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT
gluPerspective(45, (app.size[0]/app.size[1]), 0.1, 50)
glTranslatef(0, 10, -5)
glRotatef(0, 0, 0, 0)
# Setup objects
cube = OpenGL_Cube()

How can I "blit" my Pygame game onto an OpenGL surface?

I built my entire game around Pygame and want to put it on Steam. I learned at the end that I would need OpenGL support to be able to run Steam's Overlay. The code to initialize the display:
screen = pygame.display.set_mode((screen_width, screen_height), HWSURFACE | DOUBLEBUF | OPENGL)
Is there any way that I can create an OpenGL surface and blit my entire game onto that surface, so I can get OpenGL functionality (the Steam Overlay), without having to redo a lot of code and recreate a lot of the game? The game doesn't use a lot of resources, so I don't think there will be much of a lag (hopefully), so it's definitely a route I'd like to try.
Do I have any options here, aside from redoing the game in a different library?
Based on this question: Draw rectangle over texture OpenGL which discussed texture-mapping an OpenGL rectangle in PyGame ~
Here is some code which draws to an "off screen" PyGame surface. On each main-loop iteration, that surface is converted to an OpenGL texture map. This texture map is then mapped onto a rectangle which fills the screen.
The code is a fairly simple example, and perhaps you will need to optimise it a bit.
import pygame
import sys
from OpenGL.GL import *
from pygame.locals import *
# set pygame screen
pygame.init()
pygame.display.set_mode((500, 500), OPENGL | DOUBLEBUF)
pygame.display.init()
info = pygame.display.Info()
#colours
MIDNIGHT = ( 15, 0, 100 )
BUTTER = ( 255, 245, 100 )
# basic opengl configuration
glViewport(0, 0, info.current_w, info.current_h)
glDepthRange(0, 1)
glMatrixMode(GL_PROJECTION)
glMatrixMode(GL_MODELVIEW)
glLoadIdentity()
glShadeModel(GL_SMOOTH)
glClearColor(0.0, 0.0, 0.0, 0.0)
glClearDepth(1.0)
glDisable(GL_DEPTH_TEST)
glDisable(GL_LIGHTING)
glDepthFunc(GL_LEQUAL)
glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST)
glEnable(GL_BLEND)
###
### Function to convert a PyGame Surface to an OpenGL Texture
### Maybe it's not necessary to perform each of these operations
### every time.
###
texID = glGenTextures(1)
def surfaceToTexture( pygame_surface ):
global texID
rgb_surface = pygame.image.tostring( pygame_surface, 'RGB')
glBindTexture(GL_TEXTURE_2D, texID)
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST)
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST)
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP)
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP)
surface_rect = pygame_surface.get_rect()
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, surface_rect.width, surface_rect.height, 0, GL_RGB, GL_UNSIGNED_BYTE, rgb_surface)
glGenerateMipmap(GL_TEXTURE_2D)
glBindTexture(GL_TEXTURE_2D, 0)
# create pygame clock
clock = pygame.time.Clock()
# make an offscreen surface for drawing PyGame to
offscreen_surface = pygame.Surface((info.current_w, info.current_h))
text_font = pygame.font.Font( None, 30 ) # some default font
done = False
while not done:
# get quit event
for event in pygame.event.get():
if event.type == QUIT:
done = True
# Do all the PyGame operations to the offscreen surface
# So any backgrounds, sprites, etc. will get drawn to the offscreen
# rather than to the default window/screen.
offscreen_surface.fill( MIDNIGHT )
# write some nonsense to put something changing on the screen
words = text_font.render( "β-Moé-Moé count: "+str( pygame.time.get_ticks() ), True, BUTTER )
offscreen_surface.blit( words, (50, 250) )
# prepare to render the texture-mapped rectangle
glClear(GL_COLOR_BUFFER_BIT)
glLoadIdentity()
glDisable(GL_LIGHTING)
glEnable(GL_TEXTURE_2D)
#glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)
#glClearColor(0, 0, 0, 1.0)
# draw texture openGL Texture
surfaceToTexture( offscreen_surface )
glBindTexture(GL_TEXTURE_2D, texID)
glBegin(GL_QUADS)
glTexCoord2f(0, 0); glVertex2f(-1, 1)
glTexCoord2f(0, 1); glVertex2f(-1, -1)
glTexCoord2f(1, 1); glVertex2f(1, -1)
glTexCoord2f(1, 0); glVertex2f(1, 1)
glEnd()
pygame.display.flip()
clock.tick(60)
pygame.quit()

Draw rectangle over texture OpenGL

I'm using python 3 with pygame and OpenGL to try to accomplish what I thought it would be a simple task: Drawing a rectangle.
The idea is to have a white rectangle over (or bellow) a transparent texture, but whenever I add the texture to the screen the rectangle vanishes, whether I render it before or after the texture.
Bellow is a sample code displaying the problem (you can add any Player1.png image of your choice, the problem will remain the same - at least in my computer)
import pygame
import sys
from OpenGL.GL import *
from pygame.locals import *
# set pygame screen
pygame.display.set_mode((500, 500), OPENGL | DOUBLEBUF)
info = pygame.display.Info()
# basic opengl configuration
glViewport(0, 0, info.current_w, info.current_h)
glDepthRange(0, 1)
glMatrixMode(GL_PROJECTION)
glMatrixMode(GL_MODELVIEW)
glLoadIdentity()
glShadeModel(GL_SMOOTH)
glClearColor(0.0, 0.0, 0.0, 0.0)
glClearDepth(1.0)
glDisable(GL_DEPTH_TEST)
glDisable(GL_LIGHTING)
glDepthFunc(GL_LEQUAL)
glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST)
glEnable(GL_BLEND)
# load texture
surf = pygame.image.load('Player1.png')
s = pygame.image.tostring(surf, 'RGBA')
texID = glGenTextures(1)
glBindTexture(GL_TEXTURE_2D, texID)
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST)
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST)
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP)
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP)
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 142, 65, 0, GL_RGBA, GL_UNSIGNED_BYTE, s)
glGenerateMipmap(GL_TEXTURE_2D)
glBindTexture(GL_TEXTURE_2D, 0)
# create pygame clock
MAINCLOCK = pygame.time.Clock()
# init screen
pygame.display.init()
while True:
# get quit event
for event in pygame.event.get():
if event.type == QUIT:
pygame.quit()
sys.exit()
# prepare to render screen
glClear(GL_COLOR_BUFFER_BIT)
glLoadIdentity()
glDisable(GL_LIGHTING)
glEnable(GL_TEXTURE_2D)
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)
glClearColor(0, 0, 0, 1.0)
# draw texture
glBindTexture(GL_TEXTURE_2D, texID)
glBegin(GL_QUADS)
glTexCoord2f(0, 0); glVertex2f(-1, -1)
glTexCoord2f(0, 1); glVertex2f(-1, 1)
glTexCoord2f(1, 1); glVertex2f(1, 1)
glTexCoord2f(1, 0); glVertex2f(1, -1)
glEnd()
# draw rectangle
glColor3fv((1, 1, 1))
glRectf(-1, 1, 0, 0.5)
pygame.display.flip()
MAINCLOCK.tick(60)
It most likely has to do something on how OpenGL treats Textures vs Rects, but I'm not sure what.
BTW: I know the image is upside down
Thanks in advance
You have to enable two-dimensional texturing before you draw the texture, as you do it (glEnable(GL_TEXTURE_2D)).
But you have to disable two-dimensional texturing again, before you draw the rectangle:
# draw rectangle
glDisable(GL_TEXTURE_2D)
glColor3fv((1, 1, 1))
glRectf(-1, 1, 0, 0.5)
Note, the texture is still bound, when you draw the rectangle. Since you do not provide texture coordinates, when you draw the rectangle. This causes that the current texture coordinate is applied to the rectangle and a single texel is drawn all over the rectangle.
e.g. The last texture coordinate set was glTexCoord2f(1, 0):
Further note, if you would change the color for the rectangle, then the entire texture get tint by this color. If texturing is enabled, then by default the color of the texel is multiplied by the current color, because by default the texture environment mode (GL_TEXTURE_ENV_MODE) is GL_MODULATE. See glTexEnv.
glDisable(GL_TEXTURE_2D)
glColor3fv((1, 0, 0)) # red
glRectf(-1, 1, 0, 0.5)
Set the "white" color before you draw the texture:
glEnable(GL_TEXTURE_2D)
glColor3fv((1, 1, 1))

How to update data with a VBO and Pyglet

I would like to make a mesh with Pyglet that is changing every frame. Therefore I need to update the vertices very often and I thought that a VBO would be the fastest way to go here (correct me if I am wrong). Below an example for Points. Is this the correct way of doing it? I read that the number of glBindBuffer calls should be minimised, but here it is called every frame. also GL_DYNAMIC_DRAW is enabled, but if I change it to GL_STATIC_DRAW it is still working. It makes me wondering if this is a correct setup for fast computation
import pyglet
import numpy as np
from pyglet.gl import *
from ctypes import pointer, sizeof
vbo_id = GLuint()
glGenBuffers(1, pointer(vbo_id))
window = pyglet.window.Window(width=800, height=800)
glClearColor(0.2, 0.4, 0.5, 1.0)
glEnableClientState(GL_VERTEX_ARRAY)
c = 0
def update(dt):
global c
c+=1
data = (GLfloat*4)(*[500+c, 100+c,300+c,200+c])
glBindBuffer(GL_ARRAY_BUFFER, vbo_id)
glBufferData(GL_ARRAY_BUFFER, sizeof(data), 0, GL_DYNAMIC_DRAW)
glBufferSubData(GL_ARRAY_BUFFER, 0, sizeof(data), data)
pyglet.clock.schedule(update)
glPointSize(10)
#window.event
def on_draw():
glClear(GL_COLOR_BUFFER_BIT)
glColor3f(0, 0, 0)
glVertexPointer(2, GL_FLOAT, 0, 0)
glDrawArrays(GL_POINTS, 0, 2)
pyglet.app.run()
You don't need to call glBufferData every single time in update - create and fill the VBO once (see setup_initial_points) and only update it with glBufferSubData. In case you are only working with a single VBO, you can also comment out the glBindBuffer call in update() (see code below).
GL_DYNAMIC_DRAW vs GL_STATIC_DRAW won't make a big difference in this example since you are pushing very few data onto the GPU.
import pyglet
from pyglet.gl import *
from ctypes import pointer, sizeof
window = pyglet.window.Window(width=800, height=800)
''' update function '''
c = 0
def update(dt):
global c
c+=1
data = calc_point(c)
# if there's only on VBO, you can comment out the 'glBindBuffer' call
glBindBuffer(GL_ARRAY_BUFFER, vbo_id)
glBufferSubData(GL_ARRAY_BUFFER, 0, sizeof(data), data)
pyglet.clock.schedule(update)
''' draw function '''
#window.event
def on_draw():
glClear(GL_COLOR_BUFFER_BIT)
glColor3f(0, 0, 0)
glVertexPointer(2, GL_FLOAT, 0, 0)
glDrawArrays(GL_POINTS, 0, 2)
''' calculate coordinates given counter 'c' '''
def calc_point(c):
data = (GLfloat*4)(*[500+c, 100+c, 300+c, 200+c])
return data
''' setup points '''
def setup_initial_points(c):
vbo_id = GLuint()
glGenBuffers(1, pointer(vbo_id))
data = calc_point(c)
glBindBuffer(GL_ARRAY_BUFFER, vbo_id)
glBufferData(GL_ARRAY_BUFFER, sizeof(data), 0, GL_DYNAMIC_DRAW)
return vbo_id
############################################
vbo_id = setup_initial_points(c)
glClearColor(0.2, 0.4, 0.5, 1.0)
glEnableClientState(GL_VERTEX_ARRAY)
glPointSize(10)
pyglet.app.run()

Categories