How can I rotate rectangles in OpenGL? - python

Rectangles become distorted when I use glRotatef. I know that it's important to bring images to the center of the screen before rotating. I'm pretty sure I'm doing that:
import os
import pygame
from PIL import Image
from pathlib import Path
from numpy import array
import pygame
import math
from pygame.locals import *
from OpenGL.GL import *
from OpenGL.GLU import *
from OpenGL.GL import glClear, GL_COLOR_BUFFER_BIT, glBegin, GL_QUADS, glEnd, glColor3f, glVertex2f, glFlush, glClearColor
os.environ['SDL_VIDEO_CENTERED'] = '1'
windowSize = (1500, 800)
Screen = pygame.display.set_mode(windowSize, pygame.DOUBLEBUF | pygame.OPENGL)
HOME = str(Path.home())
path = HOME + '\\Desktop\piston2.png'
path2 = HOME + '\\Desktop\piston3.png'
path3 = HOME + '\\Desktop\Piston Platformer2\Images\Flower1.png'
def loadTexture(texture, flip):
try:
text = Image.open(texture)
except IOError as ex:
print("Failed to open texture file: ", texture)
text = Image.open("0.png")
textData = array(list(text.getdata()))
textID = glGenTextures(1)
glPixelStorei(GL_UNPACK_ALIGNMENT, 1)
glBindTexture(GL_TEXTURE_2D, textID)
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST)
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST)
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_BORDER)
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_BORDER)
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_BASE_LEVEL, 0)
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, 0)
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, text.size[0], text.size[1], 0, GL_RGBA, GL_UNSIGNED_BYTE, textData)
text.close()
return (text.size[0], text.size[1]), textID
def make_opengl_rect(image_sizes, cur_ID, offsets, rotate):
texts = ((1, 0), (1, 1), (0, 1), (0, 0))
x_ratio = image_sizes[0]/windowSize[0]
y_ratio = image_sizes[1]/windowSize[1]
offset_x = 2*(offsets[0]/windowSize[0])
offset_y = 2*(offsets[1]/windowSize[1])
verts = [(2*x_ratio, 2*y_ratio), (2*x_ratio, 0), (0, 0), (0, 2*y_ratio)] # upright, downright, downleft, upleft
additional_offset_x, additional_offset_y = 0, 0
verts = [(x-1+offset_x+additional_offset_x, y+1-verts[0][1]-offset_y+additional_offset_y) for (x, y) in verts]
print('verts', verts)
if rotate != 0:
glPushMatrix()
glTranslatef(-verts[0][0], -verts[0][1], 0)
glRotatef(rotate, 0, 0, 1)
glTranslatef(verts[0][0], verts[0][1], 0)
glEnable(GL_TEXTURE_2D)
glBindTexture(GL_TEXTURE_2D, cur_ID)
glBegin(GL_QUADS)
for i in range(4):
glTexCoord2f(texts[i][0], texts[i][1])
glVertex2f(verts[i][0], verts[i][1])
glEnd()
print('verts', verts)
if rotate != 0:
glPopMatrix()
glDisable(GL_TEXTURE_2D)
my_image1_sizes, my_image1_ID = loadTexture(path, False)
my_image2_sizes, my_image2_ID = loadTexture(path2, False)
my_image3_sizes, my_image3_ID = loadTexture(path3, True)
glEnable(GL_BLEND)
glBlendEquation(GL_FUNC_ADD)
running = True
while running:
for event in pygame.event.get():
if event.type == pygame.QUIT:
raise SystemExit
elif event.type == pygame.KEYDOWN:
if event.key == pygame.K_ESCAPE:
raise SystemExit
#
glClear(GL_COLOR_BUFFER_BIT)
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)
#make_opengl_rect(my_image1_sizes, my_image1_ID, (100,100), 0)
#make_opengl_rect(my_image2_sizes, my_image2_ID, (100,100), 0)
make_opengl_rect(my_image3_sizes, my_image3_ID, (300,300), 340)
glFlush()
pygame.display.flip()
print('')
Am I missing something?
I attached example images.
Rotated:
Not rotated:

Your images are distorted because of the aspect ratio of the viewport. You need to set up an Orthographic projection matrix that takes the aspect ratio into account with glOrtho. Set the projection matrix once before the application loop:
w, h = Screen.get_size()
aspect = w / h
glMatrixMode(GL_PROJECTION)
glLoadIdentity()
glOrtho(-aspect, aspect, -1, 1, -1, 1)
glMatrixMode(GL_MODELVIEW)
glLoadIdentity()
running = True
while running:
for event in pygame.event.get():
if event.type == pygame.QUIT:
raise SystemExit
elif event.type == pygame.KEYDOWN:
if event.key == pygame.K_ESCAPE:
raise SystemExit
glClear(GL_COLOR_BUFFER_BIT)
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)
#make_opengl_rect(my_image1_sizes, my_image1_ID, (100,100), 0)
#make_opengl_rect(my_image2_sizes, my_image2_ID, (100,100), 0)
make_opengl_rect(my_image3_sizes, my_image3_ID, (300,300), 340)
glFlush()
pygame.display.flip()

Related

My pygame/pyopengl code seems to apply a texture to every surface

I'm throwing some code together to help me better understand python, pygame, pyopengl, and 3D rendering. I've used code from two different places and I'm integrating them by writing my own code as I go. I've textured cubes in one program and made camera movement work in another. But when I put them together, the colors are wrong and surfaces that I don't intend to texture are affected. I'm sure I'm missing something, but I can't figure it out.
Here is what the two programs look like seperately.
,
But when I put them together, I get this.
Here is my code, sorry I couldn't figure out how to attach it as a file!
import pygame
from pygame.locals import *
from OpenGL.GL import *
from OpenGL.GLU import *
import math
def tex_coord(x, y, n=4):
""" Return the bounding vertices of the texture square.
"""
m = 1.0 / n
dx = x * m
dy = y * m
return dx, dy, dx + m, dy, dx + m, dy + m, dx, dy + m
def tex_coords(top, bottom, side):
""" Return a list of the texture squares for the top, bottom and side.
"""
top = tex_coord(*top)
bottom = tex_coord(*bottom)
side = tex_coord(*side)
result = [
(top),
(bottom),
(side),
(side),
(side),
(side),
]
"""result = []
result.extend(top)
result.extend(bottom)
result.extend(side * 4)"""
return result
#block type names and location on template go here
BLOCK1 = tex_coords((3, 0), (3, 0), (3, 0))
def verts(x, y, z, n):
vertices = (
(1+(2*x), -1+(2*y), -1+(2*z)),
(1+(2*x), 1+(2*y), -1+(2*z)),
(-1+(2*x), 1+(2*y), -1+(2*z)),
(-1+(2*x), -1+(2*y), -1+(2*z)),
(1+(2*x), -1+(2*y), 1+(2*z)),
(1+(2*x), 1+(2*y), 1+(2*z)),
(-1+(2*x), -1+(2*y), 1+(2*z)),
(-1+(2*x), 1+(2*y), 1+(2*z))
)
return(vertices)
print(verts(0, 0, 0, 1))
edges = (
(0,1),
(0,3),
(0,4),
(2,1),
(2,3),
(2,7),
(6,3),
(6,4),
(6,7),
(5,1),
(5,4),
(5,7)
)
colors = (
(1,0,0),
(0,1,0),
(0,0,1),
(0,1,0),
(1,1,1),
(0,1,1),
(1,0,0),
(0,1,0),
(0,0,1),
(1,0,0),
(1,1,1),
(0,1,1),
)
surfaces = (
(0,1,2,3),
(3,2,7,6),
(6,7,5,4),
(4,5,1,0),
(1,5,7,2),
(4,0,3,6)
)
forced = False
def Cube(vx,vy,vz,block):
if not forced:
glBegin(GL_QUADS)
y = 0
for surface in surfaces:
x = 0
y+=1
for vertex in surface:
x+=1
#glColor3fv(colors[x])
glTexCoord2f(block[y-1][2*(x-1)], block[y-1][(2*x)-1])
#print(block[y-1][2*(x-1)], block[y-1][(2*x)-1])
glVertex3fv(verts(vx,vy,vz,1)[vertex])
glEnd()
glBegin(GL_LINES)
for edge in edges:
for vertex in edge:
glVertex3fv(verts(vx,vy,vz,1)[vertex])
glEnd()
else:
texX = 0.75
texY = 0.25
glBegin(GL_QUADS)
glTexCoord2f(0.0+texX, 0.0)
glVertex3f(-1.0, -1.0, 1.0)
glTexCoord2f(0.25+texX, 0.0)
glVertex3f(1.0, -1.0, 1.0)
glTexCoord2f(0.25+texX, 0.25)
glVertex3f(1.0, 1.0, 1.0)
glTexCoord2f(0.0+texX, 0.25)
glVertex3f(-1.0, 1.0, 1.0)
glEnd()
def loadTexture():
textureSurface = pygame.image.load('texture2.png')
textureData = pygame.image.tostring(textureSurface, "RGBA", 1)
width = textureSurface.get_width()
height = textureSurface.get_height()
glEnable(GL_TEXTURE_2D)
texid = glGenTextures(1)
glBindTexture(GL_TEXTURE_2D, texid)
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, width, height,
0, GL_RGBA, GL_UNSIGNED_BYTE, textureData)
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT)
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT)
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST)
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST)
return texid
pygame.init()
display = (800, 600)
scree = pygame.display.set_mode(display, DOUBLEBUF | OPENGL)
glEnable(GL_DEPTH_TEST)
glEnable(GL_LIGHTING)
glShadeModel(GL_SMOOTH)
glEnable(GL_COLOR_MATERIAL)
glColorMaterial(GL_FRONT_AND_BACK, GL_AMBIENT_AND_DIFFUSE)
glEnable(GL_LIGHT0)
glLightfv(GL_LIGHT0, GL_AMBIENT, [0.5, 0.5, 0.5, 1])
glLightfv(GL_LIGHT0, GL_DIFFUSE, [1.0, 1.0, 1.0, 1])
"""
glClearColor(0.0, 0.0, 0.0, 0.0)
glClearDepth(1.0)
glDepthMask(GL_TRUE)
glDepthFunc(GL_LESS)
glEnable(GL_DEPTH_TEST)
#glEnable(GL_CULL_FACE)
#glCullFace(GL_FRONT)
##glFrontFace(GL_CCW)
##glShadeModel(GL_SMOOTH)
glDepthRange(0.0,1.0)
"""
sphere = gluNewQuadric()
glMatrixMode(GL_PROJECTION)
gluPerspective(45, (display[0]/display[1]), 0.1, 50.0)
glMatrixMode(GL_MODELVIEW)
gluLookAt(0, -8, 0, 0, 0, 0, 0, 0, 1)
viewMatrix = glGetFloatv(GL_MODELVIEW_MATRIX)
glLoadIdentity()
# init mouse movement and center mouse on screen
displayCenter = [scree.get_size()[i] // 2 for i in range(2)]
mouseMove = [0, 0]
pygame.mouse.set_pos(displayCenter)
loadTexture()
up_down_angle = 0.0
paused = False
run = True
while run:
for event in pygame.event.get():
if event.type == pygame.QUIT:
run = False
if event.type == pygame.KEYDOWN:
if event.key == pygame.K_ESCAPE or event.key == pygame.K_RETURN:
run = False
if event.key == pygame.K_PAUSE or event.key == pygame.K_p:
paused = not paused
pygame.mouse.set_pos(displayCenter)
if not paused:
if event.type == pygame.MOUSEMOTION:
mouseMove = [event.pos[i] - displayCenter[i] for i in range(2)]
pygame.mouse.set_pos(displayCenter)
if not paused:
# get keys
keypress = pygame.key.get_pressed()
#mouseMove = pygame.mouse.get_rel()
# init model view matrix
glLoadIdentity()
# apply the look up and down
up_down_angle += mouseMove[1]*0.1
glRotatef(up_down_angle, 1.0, 0.0, 0.0)
# init the view matrix
glPushMatrix()
glLoadIdentity()
# apply the movment
if keypress[pygame.K_w]:
glTranslatef(0,0,0.1)
if keypress[pygame.K_s]:
glTranslatef(0,0,-0.1)
if keypress[pygame.K_d]:
glTranslatef(-0.1,0,0)
if keypress[pygame.K_a]:
glTranslatef(0.1,0,0)
if keypress[pygame.K_LSHIFT]:
glTranslatef(0,0.5,0)
if keypress[pygame.K_SPACE]:
glTranslatef(0,-0.5,0)
# apply the left and right rotation
glRotatef(mouseMove[0]*0.1, 0.0, 1.0, 0.0)
# multiply the current matrix by the get the new view matrix and store the final vie matrix
glMultMatrixf(viewMatrix)
viewMatrix = glGetFloatv(GL_MODELVIEW_MATRIX)
# apply view matrix
glPopMatrix()
glMultMatrixf(viewMatrix)
#glLightfv(GL_LIGHT0, GL_POSITION, [1, -1, 1, 0])
glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT)
glPushMatrix()
Cube(0,0,0,BLOCK1)
Cube(1,0,0,BLOCK1)
Cube(0,1,0,BLOCK1)
Cube(0,0,1,BLOCK1)
Cube(-2,0,0,BLOCK1)
glColor4f(0.5, 0.5, 0.5, 1)
glBegin(GL_QUADS)
glVertex3f(-10, -10, -2)
glVertex3f(10, -10, -2)
glVertex3f(10, 10, -2)
glVertex3f(-10, 10, -2)
glEnd()
glTranslatef(-1.5, 0, 0)
glColor4f(0.5, 0.2, 0.2, 1)
gluSphere(sphere, 1.0, 32, 16)
glTranslatef(3, 0, 0)
glColor4f(0.2, 0.2, 0.5, 1)
gluSphere(sphere, 1.0, 32, 16)
glPopMatrix()
pygame.display.flip()
pygame.time.wait(10)
pygame.quit()
I'd be very thankful if someone could explain this to me!
Edit: Thank you Rabbid76!!!
Here is a picture, and my working code.
import pygame
from pygame.locals import *
from OpenGL.GL import *
from OpenGL.GLU import *
import math
def tex_coord(x, y, n=4):
""" Return the bounding vertices of the texture square.
"""
m = 1.0 / n
dx = x * m
dy = y * m
return dx, dy, dx + m, dy, dx + m, dy + m, dx, dy + m
def tex_coords(top, bottom, side):
""" Return a list of the texture squares for the top, bottom and side.
"""
top = tex_coord(*top)
bottom = tex_coord(*bottom)
side = tex_coord(*side)
result = [
(top),
(bottom),
(side),
(side),
(side),
(side),
]
"""result = []
result.extend(top)
result.extend(bottom)
result.extend(side * 4)"""
return result
#block type names and location on template go here
BLOCK1 = tex_coords((3, 0), (3, 0), (3, 0))
def verts(x, y, z, n):
vertices = (
(1+(2*x), -1+(2*y), -1+(2*z)),
(1+(2*x), 1+(2*y), -1+(2*z)),
(-1+(2*x), 1+(2*y), -1+(2*z)),
(-1+(2*x), -1+(2*y), -1+(2*z)),
(1+(2*x), -1+(2*y), 1+(2*z)),
(1+(2*x), 1+(2*y), 1+(2*z)),
(-1+(2*x), -1+(2*y), 1+(2*z)),
(-1+(2*x), 1+(2*y), 1+(2*z))
)
return(vertices)
print(verts(0, 0, 0, 1))
edges = (
(0,1),
(0,3),
(0,4),
(2,1),
(2,3),
(2,7),
(6,3),
(6,4),
(6,7),
(5,1),
(5,4),
(5,7)
)
colors = (
(1,0,0),
(0,1,0),
(0,0,1),
(0,1,0),
(1,1,1),
(0,1,1),
(1,0,0),
(0,1,0),
(0,0,1),
(1,0,0),
(1,1,1),
(0,1,1),
)
surfaces = (
(0,1,2,3),
(3,2,7,6),
(6,7,5,4),
(4,5,1,0),
(1,5,7,2),
(4,0,3,6)
)
forced = False
def Cube(vx,vy,vz,block):
if not forced:
glBegin(GL_QUADS)
y = 0
for surface in surfaces:
x = 0
y+=1
for vertex in surface:
x+=1
#glColor3fv(colors[x])
glTexCoord2f(block[y-1][2*(x-1)], block[y-1][(2*x)-1])
#print(block[y-1][2*(x-1)], block[y-1][(2*x)-1])
glVertex3fv(verts(vx,vy,vz,1)[vertex])
glEnd()
glBegin(GL_LINES)
for edge in edges:
for vertex in edge:
glVertex3fv(verts(vx,vy,vz,1)[vertex])
glEnd()
else:
texX = 0.75
texY = 0.25
glBegin(GL_QUADS)
glTexCoord2f(0.0+texX, 0.0)
glVertex3f(-1.0, -1.0, 1.0)
glTexCoord2f(0.25+texX, 0.0)
glVertex3f(1.0, -1.0, 1.0)
glTexCoord2f(0.25+texX, 0.25)
glVertex3f(1.0, 1.0, 1.0)
glTexCoord2f(0.0+texX, 0.25)
glVertex3f(-1.0, 1.0, 1.0)
glEnd()
def loadTexture():
textureSurface = pygame.image.load('texture2.png')
textureData = pygame.image.tostring(textureSurface, "RGBA", 1)
width = textureSurface.get_width()
height = textureSurface.get_height()
glColor3f(0.5, 0.5, 0.5)
glEnable(GL_TEXTURE_2D)
texid = glGenTextures(1)
glBindTexture(GL_TEXTURE_2D, texid)
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, width, height,
0, GL_RGBA, GL_UNSIGNED_BYTE, textureData)
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT)
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT)
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST)
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST)
return texid
glDisable(GL_TEXTURE_2D)
pygame.init()
display = (800, 600)
scree = pygame.display.set_mode(display, DOUBLEBUF | OPENGL)
glEnable(GL_DEPTH_TEST)
glEnable(GL_LIGHTING)
glShadeModel(GL_SMOOTH)
glEnable(GL_COLOR_MATERIAL)
glColorMaterial(GL_FRONT_AND_BACK, GL_AMBIENT_AND_DIFFUSE)
glEnable(GL_LIGHT0)
glLightfv(GL_LIGHT0, GL_AMBIENT, [0.5, 0.5, 0.5, 1])
glLightfv(GL_LIGHT0, GL_DIFFUSE, [1.0, 1.0, 1.0, 1])
"""
glClearColor(0.0, 0.0, 0.0, 0.0)
glClearDepth(1.0)
glDepthMask(GL_TRUE)
glDepthFunc(GL_LESS)
glEnable(GL_DEPTH_TEST)
#glEnable(GL_CULL_FACE)
#glCullFace(GL_FRONT)
##glFrontFace(GL_CCW)
##glShadeModel(GL_SMOOTH)
glDepthRange(0.0,1.0)
"""
sphere = gluNewQuadric()
glMatrixMode(GL_PROJECTION)
gluPerspective(45, (display[0]/display[1]), 0.1, 50.0)
glMatrixMode(GL_MODELVIEW)
gluLookAt(0, -8, 0, 0, 0, 0, 0, 0, 1)
viewMatrix = glGetFloatv(GL_MODELVIEW_MATRIX)
glLoadIdentity()
# init mouse movement and center mouse on screen
displayCenter = [scree.get_size()[i] // 2 for i in range(2)]
mouseMove = [0, 0]
pygame.mouse.set_pos(displayCenter)
loadTexture()
up_down_angle = 0.0
paused = False
run = True
while run:
for event in pygame.event.get():
if event.type == pygame.QUIT:
run = False
if event.type == pygame.KEYDOWN:
if event.key == pygame.K_ESCAPE or event.key == pygame.K_RETURN:
run = False
if event.key == pygame.K_PAUSE or event.key == pygame.K_p:
paused = not paused
pygame.mouse.set_pos(displayCenter)
if not paused:
if event.type == pygame.MOUSEMOTION:
mouseMove = [event.pos[i] - displayCenter[i] for i in range(2)]
pygame.mouse.set_pos(displayCenter)
if not paused:
# get keys
keypress = pygame.key.get_pressed()
#mouseMove = pygame.mouse.get_rel()
# init model view matrix
glLoadIdentity()
# apply the look up and down
up_down_angle += mouseMove[1]*0.1
glRotatef(up_down_angle, 1.0, 0.0, 0.0)
# init the view matrix
glPushMatrix()
glLoadIdentity()
# apply the movment
if keypress[pygame.K_w]:
glTranslatef(0,0,0.1)
if keypress[pygame.K_s]:
glTranslatef(0,0,-0.1)
if keypress[pygame.K_d]:
glTranslatef(-0.1,0,0)
if keypress[pygame.K_a]:
glTranslatef(0.1,0,0)
if keypress[pygame.K_LSHIFT]:
glTranslatef(0,0.5,0)
if keypress[pygame.K_SPACE]:
glTranslatef(0,-0.5,0)
# apply the left and right rotation
glRotatef(mouseMove[0]*0.1, 0.0, 1.0, 0.0)
# multiply the current matrix by the get the new view matrix and store the final vie matrix
glMultMatrixf(viewMatrix)
viewMatrix = glGetFloatv(GL_MODELVIEW_MATRIX)
# apply view matrix
glPopMatrix()
glMultMatrixf(viewMatrix)
#glLightfv(GL_LIGHT0, GL_POSITION, [1, -1, 1, 0])
glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT)
glPushMatrix()
glEnable(GL_TEXTURE_2D)
Cube(0,0,0,BLOCK1)
Cube(1,0,0,BLOCK1)
Cube(0,1,0,BLOCK1)
Cube(0,0,1,BLOCK1)
Cube(-2,0,0,BLOCK1)
glDisable(GL_TEXTURE_2D)
glColor4f(0.5, 0.5, 0.5, 1)
glBegin(GL_QUADS)
glVertex3f(-10, -10, -2)
glVertex3f(10, -10, -2)
glVertex3f(10, 10, -2)
glVertex3f(-10, 10, -2)
glEnd()
glTranslatef(-1.5, 0, 0)
glColor4f(0.5, 0.2, 0.2, 1)
gluSphere(sphere, 1.0, 32, 16)
glTranslatef(3, 0, 0)
glColor4f(0.2, 0.2, 0.5, 1)
gluSphere(sphere, 1.0, 32, 16)
glColor3f(1, 1, 1)
glPopMatrix()
pygame.display.flip()
pygame.time.wait(10)
pygame.quit()
OpenGL is a state engine. A state is kept until it is changed again. Two-dimensional texturing can be enabled and disabled, see glEnable.
When texturing is activated, by default the color of the pixel is multiplied by the current color, because by default the texture environment mode (GL_TEXTURE_ENV_MODE) is GL_MODULATE. See glTexEnv.
This causes that the color of the piles of the texture is "mixed" by the last color which you have set by glColor4f.
Set a "white" color and enable texturing before you render an object with a texture. Disable texturing before you draw an object with colors:
glColor3f(1.0f, 1.0f, 1.0f)
glEnable(GL_TEXTURE_2D)
# draw object with texture
# [...]
glDisable(GL_TEXTURE_2D)
# draw object with color
# [...]
Changes in your code:
forced = False
def Cube(vx,vy,vz,block):
glColor4f(1, 1, 1, 1) # <--
glEnable(GL_TEXTURE_2D)
if not forced:
glBegin(GL_QUADS)
for y, surface in enumerate(surfaces):
for x, vertex in enumerate(surface):
glTexCoord2f(block[y-1][2*(x-1)], block[y-1][(2*x)-1])
glVertex3fv(verts(vx,vy,vz,1)[vertex])
glEnd()
glDisable(GL_TEXTURE_2D) # <--
glColor4f(0, 0, 0, 1)
glBegin(GL_LINES)
for edge in edges:
for vertex in edge:
glVertex3fv(verts(vx,vy,vz,1)[vertex])
glEnd()
# [...]
run = True
while run:
# [...]
Cube(0,0,0,BLOCK1)
Cube(1,0,0,BLOCK1)
Cube(0,1,0,BLOCK1)
Cube(0,0,1,BLOCK1)
Cube(-2,0,0,BLOCK1)
glDisable(GL_TEXTURE_2D) # <--
glColor4f(0.5, 0.5, 0.5, 1)
glBegin(GL_QUADS)
glVertex3f(-10, -10, -2)
glVertex3f(10, -10, -2)
glVertex3f(10, 10, -2)
glVertex3f(-10, 10, -2)
glEnd()
# [...]

Using textures in orthographic projection in OpenGL

I'm currently programming a primary flight display using Python and OpenGL. As I have zero experience with OpenGL (and little experience with coding in general) I followed a simple tutorial giving an introduction to OpenGL for 2D applications. With this basic knowledge, I just started to make everything out of polygons (GL_QUADS) and some copied code to draw text. Which gave me this:
The visual result is satisfactory but the performance of ~450 iterations/second is questionable with one core of a Ryzen 1700x at 100% and a GTX1080 at 75% while only running this little program (which is supposed to run on a RaspberryPI 3).
So I want to try out using textures for the whole artificial horizon and inanimate objects like the scale of the speedometer, separation lines or "crosshair".
I've made a .png file that would replace the artificial horizon (see bottom) but I have no idea how to render it. All the tutorials I've found are for C++ and add a lot of complexity to the overall program so I want to ask if there is an "easy" way to implement a movable texture for my application.
Here is a mockup of how I'm currently calculating the position of the horizon and how I draw it (full runnable code at the end):
from OpenGL.GL import *
from OpenGL.GLUT import *
from OpenGL.GLU import *
from math import tan, radians
window_width = 800
window_height = 480
class Numbers:
mp = 0 #vertical middle point of horizon in pixels
rp = 0 #vertical right point of horizon in pixels
lp = 0 #vertical left point of horizon in pixels
def calc():
# mp- (pitch*(pixels/degree))
Numbers.mp = 240 - (Data.pitch * 8)
#offset is the vertical distance in pixels from mp
#offset =tan(roll) * pixels from horizontal middle point of screen to edge of artifical horizon
offset = tan(radians(Data.roll)) * 300
Numbers.rp = Numbers.mp - offset
Numbers.lp = Numbers.mp + offset
def horizon():
#sky
glBegin(GL_QUADS)
glColor3f(58/255, 109/255, 171/255)
glVertex2f(100, Numbers.lp)
glVertex2f(700, Numbers.rp)
glColor3f(0/255, 83/255, 165/255)
glVertex2f(700, 480)
glVertex2f(100, 480)
glEnd()
#ground
glColor3f(150/255, 70/255, 0/255)
glBegin(GL_QUADS)
glVertex2f(100, 0)
glVertex2f(700, 0)
glColor3f(140/255, 90/255, 0/255)
glVertex2f(700, Numbers.rp)
glVertex2f(100, Numbers.lp)
glEnd()
#devider line
glColor3f(255/255, 255/255, 255/255)
glBegin(GL_QUADS)
glVertex2f(100, Numbers.lp-1)
glVertex2f(700, Numbers.rp-1)
glVertex2f(700, Numbers.rp+1)
glVertex2f(100, Numbers.lp+1)
glEnd()
def iterate():
glViewport(0, 0, window_width, window_height)
glMatrixMode(GL_PROJECTION)
glLoadIdentity()
glOrtho(0.0, window_width, 0.0, window_height, 0.0, 1.0)
glMatrixMode (GL_MODELVIEW)
glLoadIdentity()
def showScreen():
#glutFullScreen()
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)
glLoadIdentity()
iterate()
calc()
horizon()
glutSwapBuffers()
if __name__ == "__main__":
glutInit()
glutInitDisplayMode(GLUT_RGBA)
glutInitWindowSize(window_width, window_height)
glutInitWindowPosition(10, 10)
wind = glutCreateWindow(b"Pi2Fly")
glutDisplayFunc(showScreen)
glutIdleFunc(showScreen)
glutMainLoop()
The real image goes from 180 to 180 to but that would be too big for SO
full code:
from OpenGL.GL import glVertex2f, glColor3f, glBegin, glEnd, GL_QUADS, GL_TRIANGLES
from OpenGL.GL import *
from OpenGL.GLUT import *
from OpenGL.GLU import *
from time import sleep, perf_counter, time
from math import tan, radians
import pygame
window_width = 800
window_height = 480
pygame.font.init()
font1 = pygame.font.Font (None, 40)
font2 = pygame.font.Font (None, 20)
font3 = pygame.font.Font (None, 25)
font4 = pygame.font.Font (None, 45)
class Numbers:
mp = 0
rp = 0
lp = 0
class Data:
roll = 20
pitch = 0
airspeed = 160
altitude = 4500
altitude_last = 0
altitude_now = 0
altitude_delta = 0
altitude_delta_table = 30 * [0]
time_last = 0
time_now = 0
c1 = True
c2 = True
c3 = 0
class FPS:
c = 0
fps = 0
def fps():
FPS.c += 1
try: FPS.last_clock = FPS.this_clock
except:FPS.last_clock = perf_counter()
FPS.this_clock = perf_counter()
if round(FPS.this_clock) > round(FPS.last_clock):
FPS.fps = FPS.c
FPS.c = 0
color = (255,198,0, 255)
drawText(5, 9, str(FPS.fps), color, font1)
drawText(55, 10, "FPS", color, font2)
def get_data():
#dummy data for artifical horizon
#pitch
if Data.pitch < 20 and Data.c1: Data.pitch += 0.03
if Data.pitch >= 20 and Data.c1: Data.c1 = False
if Data.pitch < 21 and not Data.c1: Data.pitch = Data.pitch - 0.03
if Data.pitch < -20: Data.c1 = True
#roll
if Data.roll < 20 and Data.c2: Data.roll += 0.02
if Data.roll >= 20 and Data.c2: Data.c2 = False
if Data.roll < 21 and not Data.c2: Data.roll = Data.roll - 0.02
if Data.roll < -20: Data.c2 = True
#airspeed
if Data.pitch > 0: Data.airspeed = Data.airspeed - 0.004 * Data.pitch
elif Data.pitch < 0: Data.airspeed = Data.airspeed + 0.004 * abs(Data.pitch)
#altitude
if Data.pitch > 0: Data.altitude = Data.altitude + 0.01 * Data.pitch
elif Data.pitch < 0: Data.altitude = Data.altitude - 0.01 * abs(Data.pitch)
#altitude_delta
Data.time_now = perf_counter()
if Data.time_now >= Data.time_last + 0.01:
if Data.c3 == 30: Data.c3 = 0
Data.time_last = Data.time_now
Data.altitude_last = Data.altitude_now
Data.altitude_now = Data.altitude
Data.altitude_delta_table[Data.c3] = Data.altitude_now - Data.altitude_last
Data.c3 +=1
Data.altitude_delta = (sum(Data.altitude_delta_table)/len(Data.altitude_delta_table))
def calc():
Numbers.mp = 240 - (Data.pitch * 8)
offset = tan(radians(Data.roll)) * 300
Numbers.rp = Numbers.mp - offset
Numbers.lp = Numbers.mp + offset
def frame():
#frame color = grey
glColor3f(100/255, 100/255, 100/255)
#horizon right line
glBegin(GL_QUADS)
glVertex2f(99, 0)
glVertex2f(100, 0)
glVertex2f(100, window_height)
glVertex2f(99, window_height)
glEnd()
#horizon left line
glBegin(GL_QUADS)
glVertex2f(700, 0)
glVertex2f(701, 0)
glVertex2f(701, window_height)
glVertex2f(700, window_height)
glEnd()
#right upper divider
glBegin(GL_QUADS)
glVertex2f(0, 430)
glVertex2f(100, 430)
glVertex2f(100, 431)
glVertex2f(0, 431)
glEnd()
#left upper divider
glBegin(GL_QUADS)
glVertex2f(700, 430)
glVertex2f(800, 430)
glVertex2f(800, 431)
glVertex2f(700, 431)
glEnd()
#right lower divider
glBegin(GL_QUADS)
glVertex2f(0, 50)
glVertex2f(100, 50)
glVertex2f(100, 51)
glVertex2f(0, 51)
glEnd()
#left lower divider
glBegin(GL_QUADS)
glVertex2f(700, 50)
glVertex2f(800, 50)
glVertex2f(800, 51)
glVertex2f(700, 51)
glEnd()
def horizon():
#sky
glBegin(GL_QUADS)
glColor3f(58/255, 109/255, 171/255)
glVertex2f(100, Numbers.lp)
glVertex2f(700, Numbers.rp)
glColor3f(0/255, 83/255, 165/255)
glVertex2f(700, 480)
glVertex2f(100, 480)
glEnd()
#ground
glColor3f(150/255, 70/255, 0/255)
glBegin(GL_QUADS)
glVertex2f(100, 0)
glVertex2f(700, 0)
glColor3f(140/255, 90/255, 0/255)
glVertex2f(700, Numbers.rp)
glVertex2f(100, Numbers.lp)
glEnd()
#devider line
glColor3f(255/255, 255/255, 255/255)
glBegin(GL_QUADS)
glVertex2f(100, Numbers.lp-1)
glVertex2f(700, Numbers.rp-1)
glVertex2f(700, Numbers.rp+1)
glVertex2f(100, Numbers.lp+1)
glEnd()
#crosshair
#indicator triangle shadow
glColor3f(187/255, 107/255, 1/255)
glBegin(GL_QUADS)
glVertex2f(400, 230)
glVertex2f(365, 215)
glVertex2f(400, 227)
glVertex2f(435, 215)
glEnd()
# indicator triangle
glColor3f(255/255, 198/255, 0/255)
glBegin(GL_QUADS)
glVertex2f(400, 240)
glVertex2f(365, 215)
glVertex2f(400, 230)
glVertex2f(435, 215)
glEnd()
#yellow indicator line
glColor3f(255/255, 198/255, 0/255)
glBegin(GL_QUADS)
glVertex2f(320, 241)
glVertex2f(320, 239)
glVertex2f(350, 239)
glVertex2f(350, 241)
glEnd()
glBegin(GL_QUADS)
glVertex2f(450, 241)
glVertex2f(450, 239)
glVertex2f(480, 239)
glVertex2f(480, 241)
glEnd()
color = (255, 255, 255, 0)
drawText(110, 10, str("roll: "+str(round(Data.roll))), color, font3)
drawText(620, 10, str("pitch: "+str(round(Data.pitch))), color, font3)
def speedometer():
#dial
ssp = 55 #start spacer
sp = 27 #spacer
color = (255,255,255,100)
drawText(14, ssp+sp*0, "0", color, font3)
drawText(10, ssp+sp*1, "20", color, font3)
drawText(10, ssp+sp*2, "40", color, font3)
drawText(10, ssp+sp*3, "60", color, font3)
drawText(10, ssp+sp*4, "80", color, font3)
drawText(5, ssp+sp*5, "100", color, font3)
drawText(5, ssp+sp*6, "120", color, font3)
drawText(5, ssp+sp*7, "140", color, font3)
drawText(5, ssp+sp*8, "160", color, font3)
drawText(5, ssp+sp*9, "180", color, font3)
drawText(5, ssp+sp*10, "200", color, font3)
drawText(5, ssp+sp*11, "220", color, font3)
drawText(5, ssp+sp*12, "240", color, font3)
drawText(5, ssp+sp*13, "260", color, font3)
#green area
glBegin(GL_QUADS)
glColor3f(35/255, 150/255, 29/255)
glVertex2f(40, 142)
glVertex2f(99, 142)
glVertex2f(99, 287)
glVertex2f(40, 287)
glEnd()
#yellow area
glBegin(GL_QUADS)
glColor3f(150/255, 100/255, 27/255)
glVertex2f(40, 287)
glVertex2f(99, 287)
glVertex2f(99, 360)
glVertex2f(40, 360)
glEnd()
#red area
glBegin(GL_QUADS)
glColor3f(151/255, 43/255, 29/255)
glVertex2f(40, 360)
glVertex2f(99, 360)
glVertex2f(99, 374)
glVertex2f(40, 374)
glEnd()
ll=35
l=15
w=1
ssp=64
glColor3f(255/255, 255/255, 255/255)
for x in range(0, 14):
ind_lines(ll, l, w, ssp+sp*x)
#knots
color = (0, 241, 250, 255)
drawText(60, 435, "kn", color, font4)
def speedometer_indicator():
ypos = 63 + round(Data.airspeed) * 1.35
glColor3f(0/255, 241/255, 250/255)
ind_lines(40, 59, 3, ypos)
color = (0, 241, 250, 255)
drawText(5, 435, str(round(Data.airspeed)), color, font4)
def altimeter():
color = (0, 241, 250, 255)
drawText(705, 435, str(round(Data.altitude)), color, font4)
drawText(784, 435, "m", color, font3)
def variometer():
color = (0, 241, 250, 255)
drawText(710, 10, str(round((abs(Data.altitude_delta*10)), 1)), color, font4)
drawText(765, 10, "m/s", color, font3)
if Data.altitude_delta*10 > 0:
glBegin(GL_TRIANGLES)
glVertex2f(767, 30)
glVertex2f(793, 30)
glVertex2f(780, 45)
glEnd()
if Data.altitude_delta*10 < 0:
glBegin(GL_TRIANGLES)
glVertex2f(765, 45)
glVertex2f(795, 45)
glVertex2f(780, 30)
glEnd()
def ind_lines(ll, l, w, sp): #ll=left limit,l=lenght, w=width, sp=spacer
#indicator lines
glBegin(GL_QUADS)
glVertex2f(ll, sp)
glVertex2f(ll+l, sp)
glVertex2f(ll+l, sp+w)
glVertex2f(ll, sp+w)
glEnd()
def drawText(xpos, ypos, textString, color, font):
textSurface = font.render(textString, True, color, (0,0,0,1))
textData = pygame.image.tostring(textSurface, "RGBA", True)
glRasterPos2d(xpos,ypos)
glDrawPixels(textSurface.get_width(), textSurface.get_height(), GL_RGBA, GL_UNSIGNED_BYTE, textData)
def draw_once():
speedometer()
frame()
def draw():
horizon()
speedometer_indicator()
altimeter()
variometer()
def iterate():
glViewport(0, 0, window_width, window_height)
glMatrixMode(GL_PROJECTION)
glLoadIdentity()
glOrtho(0.0, window_width, 0.0, window_height, 0.0, 1.0)
glMatrixMode (GL_MODELVIEW)
glLoadIdentity()
def showScreen():
#glutFullScreen()
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)
glLoadIdentity()
iterate()
try:get_data()
except Exception as e:print("get_data:\t", e)
try:calc()
except Exception as e:print("calc:\t", e)
try:draw_once()
except Exception as e:print("draw_once:\t", e)
try:draw()
except Exception as e:print("draw:\t", e)
try:fps()
except Exception as e:print("fps:\t", e)
glutSwapBuffers()
if __name__ == "__main__":
glutInit()
glutInitDisplayMode(GLUT_RGBA)
glutInitWindowSize(window_width, window_height)
glutInitWindowPosition(10, 10)
wind = glutCreateWindow(b"Pi2Fly")
glutDisplayFunc(showScreen)
glutIdleFunc(showScreen)
glutMainLoop()
You can use pygame to load an texture image:
def LoadTexture(filename):
image = pygame.image.load(filename)
datas = pygame.image.tostring(image, 'RGBA')
texID = glGenTextures(1)
glBindTexture(GL_TEXTURE_2D, texID)
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, image.get_width(), image.get_height(),
0, GL_RGBA, GL_UNSIGNED_BYTE, datas)
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR_MIPMAP_LINEAR)
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR)
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE)
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE)
glGenerateMipmap(GL_TEXTURE_2D)
glBindTexture(GL_TEXTURE_2D, 0)
return texID
Alternatively you can use Pillow and Numpy, to load a texture image, too:
from PIL import Image
import numpy
image = Image.open(filename)
imageData = numpy.array(list(image.getdata()), numpy.uint8)
texID = glGenTextures(1)
glBindTexture(GL_TEXTURE_2D, texID)
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, image.size[0], image.size[1],
0, GL_RGBA, GL_UNSIGNED_BYTE, imageData)
# [...]
When you want to dare the texture in Legacy OpenGL, then you have to:
set the texture coordinates by glTexCoord2f, before glVertex
bind the texture by glBindTexture
enable 2 dimensional textureing by glEnable(GL_TEXTURE_2D)
The following example, draws a quad on the entire viewport and wraps a texture on it. e.g.:
def DrawTexture(texID, x, y, w, h):
glBindTexture(GL_TEXTURE_2D, texID)
glEnable(GL_TEXTURE_2D)
glColor4f(1, 1, 1, 1)
glBegin(GL_QUADS)
glTexCoord2f(0, 1); glVertex2f(x, y)
glTexCoord2f(0, 0); glVertex2f(x, y+h)
glTexCoord2f(1, 0); glVertex2f(x+w, y+h)
glTexCoord2f(1, 1); glVertex2f(x+w, y)
glEnd()
glDisable(GL_TEXTURE_2D)
Note, 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.
This causes that the color of the texels of the texture is "mixed" by the last color which you have set by glColor4f.
Set a "white" color before you render the texture, to solve your issue:
glColor4f(1, 1, 1, 1)
Likewise you can change the environment mode to GL_REPLACE, instead:
glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE);
Furthermore, if the texture is partially transparent, you have to enable Blending before drawing the texture:
glEnable(GL_BLEND)
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)
DrawTexture(....)
glDisable(GL_BLEND)

How do I make OpenGL draw do a non-display surface in pygame?

I am trying to make a game that looks like a retro space shooter, where the lines are 3d wireframe.
To achieve 3D in Python, I am using pygame for the window and PyOpenGL to render the objects.
I want to get OpenGL to render to a surface (not the display surface) and then via pygame scale the surface up and render it onto the display surface. This will hopefully give the effect of a low resolution window while being fit to work on modern screens.
The thing that is stopping me from doing this is that OpenGL renders to the display surface, and I cannot find any option that allows me to change what surface it draws to.
So the process should be: OpenGL renders to small surface, pygame scales surface and draws that to the display screen, repeat.
Here is my current code:
def main():
pygame.init()
display = (500,500)
pygame.display.set_mode(display, DOUBLEBUF|OPENGL) # Create display window
gluPerspective(70, (display[0]/display[1]), 0.1, 50.0) # Setup view
glTranslatef(0.0,0.0, -5) # Set view position
while True:
for event in pygame.event.get():
if event.type == pygame.QUIT: # If X is clicked
pygame.quit() # Close
quit()
glRotatef(1, 3, 1, 1) # Rotates view
glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT) # Clears the screen
Cube() # Renders the cube onto the screen
pygame.display.flip() # Updates the display
pygame.time.wait(10)
main()
I've tried creating a surface with exactly the same settings as the display but OpenGL STILL won't render it to that surface.
You have to create a Framebuffer Object (before the main loop) with a resoultion smaller than the window resolution. See also Framebuffer Object Extension Examples:
fb_size = [50, 50]
depth_buffer_obj = glGenRenderbuffers(1)
glBindRenderbuffer(GL_RENDERBUFFER, depth_buffer_obj)
glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT24, fb_size[0], fb_size[1])
color_buffer_obj = glGenRenderbuffers(1)
glBindRenderbuffer(GL_RENDERBUFFER, color_buffer_obj)
glRenderbufferStorage(GL_RENDERBUFFER, GL_RGBA, fb_size[0], fb_size[1])
fb_obj = glGenFramebuffers(1)
glBindFramebuffer(GL_FRAMEBUFFER, fb_obj)
glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, depth_buffer_obj)
glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, color_buffer_obj)
status = glCheckFramebufferStatus(GL_FRAMEBUFFER)
if status != GL_FRAMEBUFFER_COMPLETE:
print("incomplete framebuffer object")
glBindFramebuffer(GL_FRAMEBUFFER, 0)
Se the size of the viewport to the size of the framebuffer, clear the frame buffer and render the cube to the Framebuffer:
while True:
for event in pygame.event.get():
if event.type == pygame.QUIT:
pygame.quit()
quit()
glBindFramebuffer(GL_FRAMEBUFFER, fb_obj)
glViewport (0, 0, fb_size[0], fb_size[1])
glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT)
glRotatef(1, 3, 1, 1)
Cube()
Set the viewport size to the size of the window and use glBlitFramebuffer with the filter parameter GL_NEAREST to copy the pixels from the named framebuffer object to the default framebuffer. Note it is not necessary to clear the default framebuffer, because it is completely overwritten:
while True:
# .....
glBindFramebuffer(GL_READ_FRAMEBUFFER, fb_obj)
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0)
glViewport(0, 0, 500, 500)
glBlitFramebuffer(
0, 0, fb_size[0], fb_size[1],
0, 0, 500, 500,
GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT,
GL_NEAREST)
pygame.display.flip()
pygame.time.wait(10)
Note, the line glBindFramebuffer(GL_READ_FRAMEBUFFER, fb_obj) is not necessary, because fb_obj is bound for read and draw at this point.
If your system down't support glBlitFramebuffer, the you can create a framebuffer with a texture attached to its color plane:
fb_size = [50, 50]
depth_buffer_obj = glGenRenderbuffers(1)
glBindRenderbuffer(GL_RENDERBUFFER, depth_buffer_obj)
glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT24, fb_size[0], fb_size[1])
#color_buffer_obj = glGenRenderbuffers(1)
#glBindRenderbuffer(GL_RENDERBUFFER, color_buffer_obj)
#glRenderbufferStorage(GL_RENDERBUFFER, GL_RGBA, fb_size[0], fb_size[1])
color_tex_obj = glGenTextures(1)
glBindTexture(GL_TEXTURE_2D, color_tex_obj)
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT)
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT)
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST)
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST)
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, fb_size[0], fb_size[1], 0, GL_RGBA, GL_UNSIGNED_BYTE, None)
fb_obj = glGenFramebuffers(1)
glBindFramebuffer(GL_FRAMEBUFFER, fb_obj)
glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, depth_buffer_obj)
#glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, color_buffer_obj)
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, color_tex_obj, 0)
status = glCheckFramebufferStatus(GL_FRAMEBUFFER)
if status != GL_FRAMEBUFFER_COMPLETE:
print("incomplete framebuffer object")
glBindFramebuffer(GL_FRAMEBUFFER, 0)
glBindTexture(GL_TEXTURE_2D, 0)
Render to the framebuffer and draw a quad with the the texture over the entire window to the default framebuffer:
while True:
for event in pygame.event.get():
if event.type == pygame.QUIT:
pygame.quit()
quit()
glBindFramebuffer(GL_FRAMEBUFFER, fb_obj)
glViewport (0, 0, fb_size[0], fb_size[1])
glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT)
glRotatef(1, 3, 1, 1)
gluSphere(gluNewQuadric( ), 2.0, 32, 32)
glBindFramebuffer(GL_FRAMEBUFFER, 0)
glViewport(0, 0, 500, 500)
glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT)
glMatrixMode(GL_PROJECTION)
glPushMatrix()
glLoadIdentity()
glMatrixMode(GL_MODELVIEW)
glPushMatrix()
glLoadIdentity()
#glBlitFramebuffer(
# 0, 0, fb_size[0], fb_size[1],
# 0, 0, 500, 500,
# GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT,
# GL_NEAREST)
glEnable(GL_TEXTURE_2D)
glBindTexture(GL_TEXTURE_2D, color_tex_obj)
glBegin(GL_TRIANGLE_FAN)
glTexCoord2f(0,0)
glVertex2f(-1,-1)
glTexCoord2f(1,0)
glVertex2f(1,-1)
glTexCoord2f(1,1)
glVertex2f(1,1)
glTexCoord2f(0,1)
glVertex2f(-1,1)
glEnd()
glDisable(GL_TEXTURE_2D)
glMatrixMode(GL_PROJECTION)
glPopMatrix()
glMatrixMode(GL_MODELVIEW)
glPopMatrix()
pygame.display.flip()
pygame.time.wait(10)

Overlay one 2D image onto another? PyOpenGL?

I am currently using PyOpenGL with PyGame to write code for a top-down 2D side scroller.
The code shown below will generate a 2D image in a window.
The image that is added is the background. I would now like to add sprites to the background using OpenGL. In other words, I want to overlay one 2D image on top of another. I cannot seem to find a good way to do this.
import pygame
from pygame.locals import *
from OpenGL.GL import *
from OpenGL.GLU import *
from screenDictionary import *
### Initialize variables ###
gameOn = True
dispWidth = 1920
dispHeight = 1024
bgImg = 'A1_64.png'
bgImg2 = 'HookedFront64.png'
transTuple = (255, 0, 255)
blackTuple = (100, 100, 100)
screenX = 0
screenY = 0
### Initialize display ###
def createDisplay(dispWidth, dispHeight):
pygame.init()
screen = pygame.display.set_mode((dispWidth, dispHeight), DOUBLEBUF|OPENGL)
pygame.display.set_caption('Hooked')
return screen
def createSurfaces(screen):
background = pygame.Surface(screen.get_size())
background.fill(blackTuple)
sprite = pygame.Surface(screen.get_size())
sprite.set_colorkey(transTuple)
return background
def loadScene(bgImg):
img = pygame.image.load(bgImg)
textureData = pygame.image.tostring(img, "RGB", 1)
width = img.get_width()
height = img.get_height()
bgImgGL = glGenTextures(1)
glBindTexture(GL_TEXTURE_2D, bgImgGL)
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR)
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, width, height, 0, GL_RGB, GL_UNSIGNED_BYTE, textureData)
glEnable(GL_TEXTURE_2D)
def placeScene():
glLoadIdentity()
gluPerspective(90, 1, 0.05, 100)
glTranslatef(0,0,0)
glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT)
glBegin(GL_QUADS)
glTexCoord2f(0,0)
glVertex3f(-4,-4,-4)
glTexCoord2f(0,1)
glVertex3f(-4,4,-4)
glTexCoord2f(1,1)
glVertex3f(4,4,-4)
glTexCoord2f(1,0)
glVertex3f(4,-4,-4)
glEnd()
screen = createDisplay(dispWidth, dispHeight)
background = createSurfaces(screen)
mapParams = screenDictionary(screenX, screenY)
bgImg = mapParams[0]
loadScene(bgImg)
loadScene(bgImg2)
loadScene(bgImg)
###Run the game###
while gameOn:
placeScene()
pygame.display.flip()
pygame.time.wait(1)
for event in pygame.event.get():
if event.type == pygame.QUIT:
pygame.quit()
gameOn = False
Problem solved.
Changes made: used gluOrtho2D instead of gluPerspective. Used glVertex2f instead of glVertex3f.
Most important change: Removed glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT)
This was clearing the scene every time it was executed... DUH! Still learning openGL, but will leave this here in case anyone else is dumb like me and runs into a similar problem.

Does PyGame do 3d?

I can't seem to find the answer to this question anywhere. I realize that you have to use PyOpenGL or something similar to do OpenGL stuff, but I was wondering if its possible to do very basic 3D graphics without any other dependencies.
No, Pygame is a wrapper for SDL, which is a 2D api. Pygame doesn't provide any 3D capability and probably never will.
3D libraries for Python include Panda3D and DirectPython, although they are probably quite complex to use, especially the latter.
Well, if you can do 2d you can always do 3d. All 3d really is is skewed 2 dimensional surfaces giving the impression you're looking at something with depth. The real question is can it do it well, and would you even want to. After browsing the pyGame documentation for a while, it looks like it's just an SDL wrapper. SDL is not intended for 3d programming, so the answer to the real question is, No, and I wouldn't even try.
You can do pseudo-3d games ( like "Doom" ) with pygame only:
http://code.google.com/p/gh0stenstein/
and if you browse the pygame.org site you may find more "3d" games done with python and pygame.
However, if you really want to go into 3d programming you should look into OpenGl, Blender or any other real 3d lib.
Pygame was never originally meant to do 3d, but there is a way you can do 3d with any 2d graphics library. All you need is the following function, which converts 3d points to 2d points, which allows you to make any 3d shape by just drawing lines on a screen.
def convert_to_2d(point=[0,0,0]):
return [point[0]*(point[2]*.3),point[1]*(point[2]*.3)]
This is called pseudo 3d, or 2.5d. This can be done, but may be slow, and is extremely difficult to do, so it is suggested that you use a library meant for 3d.
It does not support, but combining with PyOpenGL you can make use of the power of both, here is a full example
import pygame
from pygame.locals import *
from OpenGL.GL import *
from OpenGL.GLU import *
import random
vertices = ((1, -1, -1),(1, 1, -1),(-1, 1, -1),(-1, -1, -1),(1, -1, 1),(1, 1, 1),(-1, -1, 1),(-1, 1, 1))
edges = ((0,1),(0,3),(0,4),(2,1),(2,3),(2,7),(6,3),(6,4),(6,7),(5,1),(5,4),(5,7))
surfaces = ((0,1,2,3),(3,2,7,6),(6,7,5,4),(4,5,1,0),(1,5,7,2),(4,0,3,6))
colors = ((1,0,0),(0,1,0),(0,0,1),(0,1,0),(1,1,1),(0,1,1),(1,0,0),(0,1,0),(0,0,1),(1,0,0),(1,1,1),(0,1,1),)
def set_vertices(max_distance, min_distance = -20):
x_value_change = random.randrange(-10,10)
y_value_change = random.randrange(-10,10)
z_value_change = random.randrange(-1*max_distance,min_distance)
new_vertices = []
for vert in vertices:
new_vert = []
new_x = vert[0] + x_value_change
new_y = vert[1] + y_value_change
new_z = vert[2] + z_value_change
new_vert.append(new_x)
new_vert.append(new_y)
new_vert.append(new_z)
new_vertices.append(new_vert)
return new_vertices
def Cube(vertices):
glBegin(GL_QUADS)
for surface in surfaces:
x = 0
for vertex in surface:
x+=1
glColor3fv(colors[x])
glVertex3fv(vertices[vertex])
glEnd()
glBegin(GL_LINES)
for edge in edges:
for vertex in edge:
glVertex3fv(vertices[vertex])
glEnd()
def main():
pygame.init()
display = (800,600)
pygame.display.set_mode(display, DOUBLEBUF|OPENGL)
max_distance = 100
gluPerspective(45, (display[0]/display[1]), 0.1, max_distance)
glTranslatef(random.randrange(-5,5),random.randrange(-5,5), -40)
#object_passed = False
x_move = 0
y_move = 0
cube_dict = {}
for x in range(50):
cube_dict[x] =set_vertices(max_distance)
#glRotatef(25, 2, 1, 0)
x = glGetDoublev(GL_MODELVIEW_MATRIX)
camera_x = x[3][0]
camera_y = x[3][1]
camera_z = x[3][2]
button_down = False
while True:
for event in pygame.event.get():
if event.type == pygame.QUIT:
pygame.quit()
quit()
if event.type == pygame.MOUSEMOTION:
if button_down == True:
print(pygame.mouse.get_pressed())
glRotatef(event.rel[1], 1, 0, 0)
glRotatef(event.rel[0], 0, 1, 0)
for event in pygame.mouse.get_pressed():
# print(pygame.mouse.get_pressed())
if pygame.mouse.get_pressed()[0] == 1:
button_down = True
elif pygame.mouse.get_pressed()[0] == 0:
button_down = False
glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT)
for each_cube in cube_dict:
Cube(cube_dict[each_cube])
pygame.display.flip()
pygame.time.wait(10)
main()
pygame.quit()
quit()
What you see as a 3D is actually a 2D game. After all, you are watching your screen, which (normally ;) ) is 2D. The virtual world (which is in 3D) is projected onto a plane, which is then shown on your screen. Our brains then convert that 2D image into a 3D one (like they do with the image of our eyes), making it look like it's 3D.
So it's relatively easy to make a 3D game: you just create a virtual world using a multidimensional matrix and then project it each loop on a 2D plane, which you display on your screen.
One tutorial that can put you on your way to 3D programs (using pygame) is this one .
3D rendering in Pygame without the help of other dependencies is hard to achieve and will not perform well. Pygame does not offer any functionality for drawing 3D shapes, meshes, or even perspective and lighting.
If you want to draw a 3D scene with Pygame, you need to compute the vertices using vector arithmetic and stitch the geometry together using polygons.
Example of then answer to Pygame rotating cubes around axis:
This approach won't give a satisfying performance and is only valuable for studying. 3D scenes are generated with the help of the GPU. A CPU-only approach does not achieve the required performance.
Nevertheless, nice results can be achieved with a 2.5-D approach. See the answer to How do I fix wall warping in my raycaster?:
import pygame
import math
pygame.init()
tile_size, map_size = 50, 8
board = [
'########',
'# # #',
'# # ##',
'# ## #',
'# #',
'### ###',
'# #',
'########']
def cast_rays(sx, sy, angle):
rx = math.cos(angle)
ry = math.sin(angle)
map_x = sx // tile_size
map_y = sy // tile_size
t_max_x = sx/tile_size - map_x
if rx > 0:
t_max_x = 1 - t_max_x
t_max_y = sy/tile_size - map_y
if ry > 0:
t_max_y = 1 - t_max_y
while True:
if ry == 0 or t_max_x < t_max_y * abs(rx / ry):
side = 'x'
map_x += 1 if rx > 0 else -1
t_max_x += 1
if map_x < 0 or map_x >= map_size:
break
else:
side = 'y'
map_y += 1 if ry > 0 else -1
t_max_y += 1
if map_x < 0 or map_y >= map_size:
break
if board[int(map_y)][int(map_x)] == "#":
break
if side == 'x':
x = (map_x + (1 if rx < 0 else 0)) * tile_size
y = player_y + (x - player_x) * ry / rx
direction = 'r' if x >= player_x else 'l'
else:
y = (map_y + (1 if ry < 0 else 0)) * tile_size
x = player_x + (y - player_y) * rx / ry
direction = 'd' if y >= player_y else 'u'
return (x, y), math.hypot(x - sx, y - sy), direction
def cast_fov(sx, sy, angle, fov, no_ofrays):
max_d = math.tan(math.radians(fov/2))
step = max_d * 2 / no_ofrays
rays = []
for i in range(no_ofrays):
d = -max_d + (i + 0.5) * step
ray_angle = math.atan2(d, 1)
pos, dist, direction = cast_rays(sx, sy, angle + ray_angle)
rays.append((pos, dist, dist * math.cos(ray_angle), direction))
return rays
area_width = tile_size * map_size
window = pygame.display.set_mode((area_width*2, area_width))
clock = pygame.time.Clock()
board_surf = pygame.Surface((area_width, area_width))
for row in range(8):
for col in range(8):
color = (192, 192, 192) if board[row][col] == '#' else (96, 96, 96)
pygame.draw.rect(board_surf, color, (col * tile_size, row * tile_size, tile_size - 2, tile_size - 2))
player_x, player_y = round(tile_size * 4.5) + 0.5, round(tile_size * 4.5) + 0.5
player_angle = 0
max_speed = 3
colors = {'r' : (196, 128, 64), 'l' : (128, 128, 64), 'd' : (128, 196, 64), 'u' : (64, 196, 64)}
run = True
while run:
clock.tick(30)
for event in pygame.event.get():
if event.type == pygame.QUIT:
run = False
keys = pygame.key.get_pressed()
hit_pos_front, dist_front, side_front = cast_rays(player_x, player_y, player_angle)
hit_pos_back, dist_back, side_back = cast_rays(player_x, player_y, player_angle + math.pi)
player_angle += (keys[pygame.K_RIGHT] - keys[pygame.K_LEFT]) * 0.1
speed = ((0 if dist_front <= max_speed else keys[pygame.K_UP]) - (0 if dist_back <= max_speed else keys[pygame.K_DOWN])) * max_speed
player_x += math.cos(player_angle) * speed
player_y += math.sin(player_angle) * speed
rays = cast_fov(player_x, player_y, player_angle, 60, 40)
window.blit(board_surf, (0, 0))
for ray in rays:
pygame.draw.line(window, (0, 255, 0), (player_x, player_y), ray[0])
pygame.draw.line(window, (255, 0, 0), (player_x, player_y), hit_pos_front)
pygame.draw.circle(window, (255, 0, 0), (player_x, player_y), 8)
pygame.draw.rect(window, (128, 128, 255), (400, 0, 400, 200))
pygame.draw.rect(window, (128, 128, 128), (400, 200, 400, 200))
for i, ray in enumerate(rays):
height = round(10000 / ray[2])
width = area_width // len(rays)
color = pygame.Color((0, 0, 0)).lerp(colors[ray[3]], min(height/256, 1))
rect = pygame.Rect(area_width + i*width, area_width//2-height//2, width, height)
pygame.draw.rect(window, color, rect)
pygame.display.flip()
pygame.quit()
exit()
Also see PyGameExamplesAndAnswers - Raycasting
I am aware that you asked "... but I was wondering if its possible to do very basic 3D graphics without any other dependencies.". Anyway, I will give you some additional options with other dependencies.
One way to make 3D scenes more powerful in Python is to use an OpenGL based library like pyglet or ModernGL.
However, you can use a Pygame window to create an OpenGL Context. You need to set the pygame.OPENGL flag when creating the display Surface (see pygame.display.set_mode):
window = pg.display.set_mode((width, height), pygame.OPENGL | pygame.DOUBLEBUF)
Modern OpenGL PyGame/PyOpenGL example:
import pygame
from OpenGL.GL import *
from OpenGL.GLU import *
from OpenGL.GL.shaders import *
import ctypes
import glm
glsl_vert = """
#version 330 core
layout (location = 0) in vec3 a_pos;
layout (location = 1) in vec4 a_col;
out vec4 v_color;
uniform mat4 u_proj;
uniform mat4 u_view;
uniform mat4 u_model;
void main()
{
v_color = a_col;
gl_Position = u_proj * u_view * u_model * vec4(a_pos.xyz, 1.0);
}
"""
glsl_frag = """
#version 330 core
out vec4 frag_color;
in vec4 v_color;
void main()
{
frag_color = v_color;
}
"""
class Cube:
def __init__(self):
v = [(-1,-1,-1), ( 1,-1,-1), ( 1, 1,-1), (-1, 1,-1), (-1,-1, 1), ( 1,-1, 1), ( 1, 1, 1), (-1, 1, 1)]
edges = [(0,1), (1,2), (2,3), (3,0), (4,5), (5,6), (6,7), (7,4), (0,4), (1,5), (2,6), (3,7)]
surfaces = [(0,1,2,3), (5,4,7,6), (4,0,3,7),(1,5,6,2), (4,5,1,0), (3,2,6,7)]
colors = [(1,0,0), (0,1,0), (0,0,1), (1,1,0), (1,0,1), (1,0.5,0)]
line_color = [0, 0, 0]
edge_attributes = []
for e in edges:
edge_attributes += v[e[0]]
edge_attributes += line_color
edge_attributes += v[e[1]]
edge_attributes += line_color
face_attributes = []
for i, quad in enumerate(surfaces):
for iv in quad:
face_attributes += v[iv]
face_attributes += colors[i]
self.edge_vbo = glGenBuffers(1)
glBindBuffer(GL_ARRAY_BUFFER, self.edge_vbo)
glBufferData(GL_ARRAY_BUFFER, (GLfloat * len(edge_attributes))(*edge_attributes), GL_STATIC_DRAW)
self.edge_vao = glGenVertexArrays(1)
glBindVertexArray(self.edge_vao)
glVertexAttribPointer(0, 3, GL_FLOAT, False, 6*ctypes.sizeof(GLfloat), ctypes.c_void_p(0))
glEnableVertexAttribArray(0)
glVertexAttribPointer(1, 3, GL_FLOAT, False, 6*ctypes.sizeof(GLfloat), ctypes.c_void_p(3*ctypes.sizeof(GLfloat)))
glEnableVertexAttribArray(1)
self.face_vbos = glGenBuffers(1)
glBindBuffer(GL_ARRAY_BUFFER, self.face_vbos)
glBufferData(GL_ARRAY_BUFFER, (GLfloat * len(face_attributes))(*face_attributes), GL_STATIC_DRAW)
self.face_vao = glGenVertexArrays(1)
glBindVertexArray(self.face_vao)
glVertexAttribPointer(0, 3, GL_FLOAT, False, 6*ctypes.sizeof(GLfloat), ctypes.c_void_p(0))
glEnableVertexAttribArray(0)
glVertexAttribPointer(1, 3, GL_FLOAT, False, 6*ctypes.sizeof(GLfloat), ctypes.c_void_p(3*ctypes.sizeof(GLfloat)))
glEnableVertexAttribArray(1)
def draw(self):
glEnable(GL_DEPTH_TEST)
glLineWidth(5)
glBindVertexArray(self.edge_vao)
glDrawArrays(GL_LINES, 0, 12*2)
glBindVertexArray(0)
glEnable(GL_POLYGON_OFFSET_FILL)
glPolygonOffset( 1.0, 1.0 )
glBindVertexArray(self.face_vao)
glDrawArrays(GL_QUADS, 0, 6*4)
glBindVertexArray(0)
glDisable(GL_POLYGON_OFFSET_FILL)
def set_projection(w, h):
return glm.perspective(glm.radians(45), w / h, 0.1, 50.0)
pygame.init()
window = pygame.display.set_mode((400, 300), pygame.DOUBLEBUF | pygame.OPENGL | pygame.RESIZABLE)
clock = pygame.time.Clock()
proj = set_projection(*window.get_size())
view = glm.lookAt(glm.vec3(0, 0, 5), glm.vec3(0, 0, 0), glm.vec3(0, 1, 0))
model = glm.mat4(1)
cube = Cube()
angle_x, angle_y = 0, 0
program = compileProgram(
compileShader(glsl_vert, GL_VERTEX_SHADER),
compileShader(glsl_frag, GL_FRAGMENT_SHADER))
attrib = { a : glGetAttribLocation(program, a) for a in ['a_pos', 'a_col'] }
print(attrib)
uniform = { u : glGetUniformLocation(program, u) for u in ['u_model', 'u_view', 'u_proj'] }
print(uniform)
glUseProgram(program)
run = True
while run:
clock.tick(60)
for event in pygame.event.get():
if event.type == pygame.QUIT:
run = False
elif event.type == pygame.VIDEORESIZE:
glViewport(0, 0, event.w, event.h)
proj = set_projection(event.w, event.h)
model = glm.mat4(1)
model = glm.rotate(model, glm.radians(angle_y), glm.vec3(0, 1, 0))
model = glm.rotate(model, glm.radians(angle_x), glm.vec3(1, 0, 0))
glUniformMatrix4fv(uniform['u_proj'], 1, GL_FALSE, glm.value_ptr(proj))
glUniformMatrix4fv(uniform['u_view'], 1, GL_FALSE, glm.value_ptr(view))
glUniformMatrix4fv(uniform['u_model'], 1, GL_FALSE, glm.value_ptr(model))
angle_x += 1
angle_y += 0.4
glClearColor(0.5, 0.5, 0.5, 1)
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)
cube.draw()
pygame.display.flip()
pygame.quit()
exit()
Legacy OpenGL PyGame/PyOpenGL example:
import pygame
from OpenGL.GL import *
from OpenGL.GLU import *
class Cube:
def __init__(self):
self.v = [(-1,-1,-1), ( 1,-1,-1), ( 1, 1,-1), (-1, 1,-1), (-1,-1, 1), ( 1,-1, 1), ( 1, 1, 1), (-1, 1, 1)]
self.edges = [(0,1), (1,2), (2,3), (3,0), (4,5), (5,6), (6,7), (7,4), (0,4), (1,5), (2,6), (3,7)]
self.surfaces = [(0,1,2,3), (5,4,7,6), (4,0,3,7),(1,5,6,2), (4,5,1,0), (3,2,6,7)]
self.colors = [(1,0,0), (0,1,0), (0,0,1), (1,1,0), (1,0,1), (1,0.5,0)]
def draw(self):
glEnable(GL_DEPTH_TEST)
glLineWidth(5)
glColor3fv((0, 0, 0))
glBegin(GL_LINES)
for e in self.edges:
glVertex3fv(self.v[e[0]])
glVertex3fv(self.v[e[1]])
glEnd()
glEnable(GL_POLYGON_OFFSET_FILL)
glPolygonOffset( 1.0, 1.0 )
glBegin(GL_QUADS)
for i, quad in enumerate(self.surfaces):
glColor3fv(self.colors[i])
for iv in quad:
glVertex3fv(self.v[iv])
glEnd()
glDisable(GL_POLYGON_OFFSET_FILL)
def set_projection(w, h):
glMatrixMode(GL_PROJECTION)
glLoadIdentity()
gluPerspective(45, w / h, 0.1, 50.0)
glMatrixMode(GL_MODELVIEW)
def screenshot(display_surface, filename):
size = display_surface.get_size()
buffer = glReadPixels(0, 0, *size, GL_RGBA, GL_UNSIGNED_BYTE)
screen_surf = pygame.image.fromstring(buffer, size, "RGBA")
pygame.image.save(screen_surf, filename)
pygame.init()
window = pygame.display.set_mode((400, 300), pygame.DOUBLEBUF | pygame.OPENGL | pygame.RESIZABLE)
clock = pygame.time.Clock()
set_projection(*window.get_size())
cube = Cube()
angle_x, angle_y = 0, 0
run = True
while run:
clock.tick(60)
take_screenshot = False
for event in pygame.event.get():
if event.type == pygame.QUIT:
run = False
elif event.type == pygame.VIDEORESIZE:
glViewport(0, 0, event.w, event.h)
set_projection(event.w, event.h)
elif event.type == pygame.KEYDOWN:
take_screenshot = True
glLoadIdentity()
glTranslatef(0, 0, -5)
glRotatef(angle_y, 0, 1, 0)
glRotatef(angle_x, 1, 0, 0)
angle_x += 1
angle_y += 0.4
glClearColor(0.5, 0.5, 0.5, 1)
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)
cube.draw()
if take_screenshot:
screenshot(window, "cube.png")
pygame.display.flip()
pygame.quit()
exit()
You can make like this :
def convert_2d(x, y, z, horizon):
d = 1 - (z/horizon)
return x*d, y*d
def draw_list_of_points(lst):
'''Assume that lst is a list of 3 dimentionnal points like [(0, 0, 0), (1, 6, 2)...
Let's take 200 for the horizon, it can give us a pretty clean 3D'''
for x, y, z in lst:
pygame.draw.circle(screen, color, convert_2d(x, y, z, 200), 1)
But it's not very fast. If you want fast try to implement in C++/SDL2 or C.
Pygame is not very good for 3d graphics.
It is easy to make 3D driver for PyGame. PyGame has some assets for 3D game development.
I am developing Py3D driver using PyGame now. When I finish, I'll show you link to download Py3D. I tried to make 3D game with PyGame, and I needed just small addon for PyGame. It is wrong you think you must use SDL, PyOpenGL, OpenGL, PyQt5, Tkinter. All of them are wrong for making 3D games. OpenGL and PyOpenGL or Panda3D are very hard to learn. All my games made on those drivers were awful. PyQt5 and Tkinter aren't drivers for making games, but they've got addons for it. Don't try to make any game on those drivers. All drivers where we need to use the math module are hard. You can easily make small addon for them, I think everybody can make driver for PyGame in 1-2 weeks.
If you want to stick with a python-esque language when making games, Godot is a good alternative with both 2D and 3D support, a large community, and lots of tutorials. Its custom scripting language(gdscript) has some minor differences, but overall its mostly the same. It also has support for c# and c++, and has much more features when it comes to game development.
Pygame is just a library for changing the color of pixels (and some other useful stuff for programming games). You can do this by blitting images to the screen or directly setting the colors of pixels.
Because of this, it is easy to write 2D games with pygame, as the above is all you really need. But a 3D game is just some 3D objects 'squashed' (rendered) into 2D so that it can be displayed on the screen. So, to make a 3D game using only pygame, you would have handle this rendering by yourself, including all the complex matrix maths necessary.
Not only would this run slowly because of the immense processing power involved in this, but it would require you to write a massive 3D rendering/rasterisation engine. And because of python being interpreted it would be even slower. The correct approach would be to have this process run on the GPU using (Py)opengl.
So, yes it is technically possible to do 3D using only pygame, but definitely not recommended. I would suggest you learn Panda3D or some similar 3D engine.
Simple:
Just draw a bunch of polygons like:
import pygame
screen = pygame.display.set_mode((100, 100))
While True:
screen.fill((0, 0, 0))
Pos = [(10, 10), (20, 10), (20, 20), (10, 20)]
# first side (the front) in red
pygame.draw.polygon(screen, (225, 0, 0), Pos)
# outline in white
pygame.draw.lines(screen, (225, 225, 225), Pos)
# Second side (the back) in blue
Pos2 = [(Pos[0[0]] + 2.5, Pos[0[1]] + 2.5), (Pos2[0[0]] + 5, Pos2[0[1]]), (Pos2[1[0]], Pos2[1[1]] + 5), (Pos2[0[0]], Pos2[0[1]] + 5)]
pygame.draw.polygon(screen, (0, 0, 225), Pos2)
pygame.draw.lines(screen, (225, 225, 225), Pos2)
# Third side (the left but just 2 lines(not really)) in green
Pos3 = [Pos[0], Pos2[0], Pos2[3], Pos[3]]
pygame.draw.polygon(screen, (0, 225, 0), Pos3)
pygame.draw.lines(screen, (225, 225, 225), Pos3)
# Fourth side (the right) in purple
Pos4 = [Pos[1], Pos2[1], Pos2[2], Pos[2]]
pygame.draw.polygon(screen, (225, 0, 225), Pos4)
pygame.draw.lines(screen, (225, 225, 225), Pos4)
pygame.display.flip()
& there is a simple cube & I will soon provide a link for the full code to be able to rotate the cube & resize it
This should give you an equivalent of what you would get by using OpenGL
This is what I have managed to do with just Pygame and Numpy without using OpenGL with some basic shading.
You can do 3D in PyGame but probably isn't the most efficient and fastest.

Categories

Resources