Rendering a 2D quad from a VBO with PyOpenGL? - python

How Can You Render A Quad From A VBO using PyOpenGL and Pygame?
I am trying to get an image to render on my screen using PyOoenGL and pygame, while not using any VBO's I managed to get it to display using this draw function:
def draw(texture: Texture):
texture.Bind()
glBegin(GL_QUADS)
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()
However, when I tried to change it out to render using a VBO I could not see well... anything:
def GenVBO():
# gen VBO for a quad # glBufferSubData(GL_ARRAY_BUFFER, offset, len(dataArray)*4, dataArray)
VBO = glGenBuffers(1)
glBindBuffer(GL_ARRAY_BUFFER, VBO)
glBufferData(GL_ARRAY_BUFFER, 64, None, GL_STATIC_DRAW)
glBufferSubData(GL_ARRAY_BUFFER, 0, 32, (GLfloat * 12)(-1, -1, 0, 0, 0, 0, 1, -1, 0, 1, 0, 0))
glBindBuffer(GL_ARRAY_BUFFER, 0)
return VBO
def draw(texture: Texture, VBO):
texture.Bind()
glBindBuffer(GL_ARRAY_BUFFER, VBO)
glEnableClientState(GL_VERTEX_ARRAY)
glEnableClientState(GL_TEXTURE_COORD_ARRAY)
glVertexPointer(2, GL_FLOAT, 32, None)
glTexCoordPointer(2, GL_FLOAT, 32, ctypes.c_void_p(8))
glDrawArrays(GL_QUADS, 0, 4)
I do not get any errors, just a blank screen, I have also tried to move around by changing my perspective with a movement script, but still no output.
And just in case it's relevant, here is the main script:
def main():
pygame.init()
display = (800, 600)
pygame.display.set_mode(display, DOUBLEBUF|OPENGL)
glEnable(GL_TEXTURE_2D)
glEnable(GL_BLEND)
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)
gluPerspective(45, (display[0]/display[1]), 0.1, 50.0)
glTranslatef(0.0, 0.0, -5)
texture = LoadImage("test.png")
VBO = GenVBO()
while True:
down = pygame.key.get_pressed()
for event in pygame.event.get():
if event.type == pygame.QUIT:
pygame.quit()
quit()
# (the movement script is here)
glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT)
draw(texture, VBO)
pygame.display.flip()
pygame.time.wait(10)

You have 2 possibiliets:
(x0, y0, u0, v0, x1, y1, u1, v1, ...)
In this case stride is 16 bytes, because each attribute tuple has 4*4 bytes (x, y, u, v). The offset of the vertices is 0 and the offset of the texture coordinates is 8 bytes:
bufferData = (GLfloat * 16)(-1, -1, 0, 0, 1, -1, 1, 0, 1, 1, 1, 1, -1, 1, 0, 1)
glBufferData(GL_ARRAY_BUFFER, bufferData, GL_STATIC_DRAW)
glVertexPointer(2, GL_FLOAT, 16, None)
glTexCoordPointer(2, GL_FLOAT, 16, ctypes.c_void_p(8))
(x0, y0, x1, y1, ..., u0, v0, u1, v1, ...)
In this case stride vor the vertices is 8 bytes and the stride for the texture coordinates is 8 bytes. The offset of the vertices is 0 and the offset of the texture coordinates is 32 bytes (8*4):
vertexData = (GLfloat * 8)(-1, -1, 1, -1, 1, 1, -1, 1)
textureData = (GLfloat * 8)( 0, 0, 1, 0, 1, 1, 0, 1)
glBufferData(GL_ARRAY_BUFFER, 64, None, GL_STATIC_DRAW)
glBufferSubData(GL_ARRAY_BUFFER, 0, 32, vertexData)
glBufferSubData(GL_ARRAY_BUFFER, 32, 32, textureData)
glVertexPointer(2, GL_FLOAT, 8, None)
glTexCoordPointer(2, GL_FLOAT, 8, ctypes.c_void_p(32))

Related

pyopengl blackscreen not drawing

cant seem to draw a triangle
import pygame
from pygame.locals import *
import numpy as np
from OpenGL.GL import *
pygame.init()
display = (600,600)
clock = pygame.time.Clock()
pygame.display.set_mode(display, DOUBLEBUF|OPENGL)
positions = [
-0.5, -0.5,
0, 0.5,
0.5, -0.5
]
positions = np.array(positions, dtype=np.float32)
buffer = glGenBuffers(1)
glBindBuffer(GL_ARRAY_BUFFER, buffer)
glBufferData(GL_ARRAY_BUFFER, positions.size, positions, GL_STATIC_DRAW)
glEnableVertexAttribArray(0)
glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 8, 0)
run = True
while run:
for event in pygame.event.get():
if event.type == pygame.QUIT:
run = False
pygame.quit()
if not run:
break
glDrawArrays(GL_TRIANGLES, 0, 3)
pygame.display.flip()
is this a python problem or am i writing it wrong ?
The 2nd argument of glBufferData is the buffer size in bytes:
glBufferData(GL_ARRAY_BUFFER, positions.size, positions, GL_STATIC_DRAW)
glBufferData(GL_ARRAY_BUFFER, positions.size*4, positions, GL_STATIC_DRAW)
The parameter size can be omitted:
glBufferData(GL_ARRAY_BUFFER, positions, GL_STATIC_DRAW)
If a named buffer object is bound, then the 6th parameter of glVertexAttribPointer is treated as a byte offset into the buffer object's data store. But the type of the parameter is a pointer anyway (c_void_p).
So if the offset is 0, then the 6th parameter can either be None or c_void_p(0) else the offset has to be caste to c_void_p(0):
glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 8, 0)
glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 8, None)
Complete example:
import pygame
from pygame.locals import *
import numpy as np
from OpenGL.GL import *
pygame.init()
display = (600,600)
clock = pygame.time.Clock()
pygame.display.set_mode(display, DOUBLEBUF|OPENGL)
positions = [
-0.5, -0.5,
0, 0.5,
0.5, -0.5
]
positions = np.array(positions, dtype=np.float32)
buffer = glGenBuffers(1)
glBindBuffer(GL_ARRAY_BUFFER, buffer)
glBufferData(GL_ARRAY_BUFFER, positions, GL_STATIC_DRAW)
glEnableVertexAttribArray(0)
glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 8, None)
run = True
while run:
for event in pygame.event.get():
if event.type == pygame.QUIT:
run = False
glClear(GL_COLOR_BUFFER_BIT)
glDrawArrays(GL_TRIANGLES, 0, 3)
pygame.display.flip()
pygame.quit()
It is hard to know what example will work without knowing what GL version your hardware supports (print (glGetString(GL_VERSION))), but here is a compatibility demo that should work, but you should consider modifying it to at least OpenGL3.3/GLSL 330:
import pygame
from pygame.locals import *
import numpy as np
from OpenGL.GL import *
pygame.init()
display = (600,600)
clock = pygame.time.Clock()
pygame.display.set_mode(display, DOUBLEBUF|OPENGL)
positions = [
-0.5, -0.5,
0, 0.5,
0.5, -0.5
]
vertex_shader_source = """#version 120
attribute vec2 aPos;
varying vec2 pos;
void main()
{
gl_Position = vec4(aPos.x, aPos.y, 0, 1);
pos = aPos;
}"""
fragment_shader_source = """#version 120
varying vec2 pos;
void main()
{
gl_FragColor = vec4(pos.x, pos.y,0,1);
}"""
vertex_shader = glCreateShader(GL_VERTEX_SHADER)
glShaderSource(vertex_shader, vertex_shader_source)
glCompileShader(vertex_shader)
if glGetShaderiv(vertex_shader, GL_COMPILE_STATUS) != GL_TRUE:
raise RuntimeError(glGetShaderInfoLog(vertex_shader))
fragment_shader = glCreateShader(GL_FRAGMENT_SHADER)
glShaderSource(fragment_shader, fragment_shader_source)
glCompileShader(fragment_shader)
if glGetShaderiv(fragment_shader, GL_COMPILE_STATUS) != GL_TRUE:
raise RuntimeError(glGetShaderInfoLog(fragment_shader))
shader_program = glCreateProgram()
glAttachShader(shader_program, vertex_shader)
glAttachShader(shader_program, fragment_shader)
glLinkProgram(shader_program)
positions = np.array(positions, dtype=np.float32)
VAO = glGenVertexArrays(1)
VBO = glGenBuffers(1)
glBindVertexArray(VAO)
glBindBuffer(GL_ARRAY_BUFFER, VBO)
# using positions.size * np.dtype(np.float32).itemsize
# to get the size (in bytes) of the vertex data
glBufferData(GL_ARRAY_BUFFER, positions.size * np.dtype(np.float32).itemsize, \
positions, GL_STATIC_DRAW)
glEnableVertexAttribArray(0)
aPos = glGetAttribLocation(shader_program, 'aPos')
glEnableVertexAttribArray(aPos)
# ctypes.c_void_p(0) to specify the offset (0 wont work)
glVertexAttribPointer(aPos, 2, GL_FLOAT, GL_FALSE, 0, ctypes.c_void_p(0))
if glGetProgramiv(shader_program, GL_LINK_STATUS) != GL_TRUE:
raise RuntimeError(glGetProgramInfoLog(shader_program))
glClearColor(0,0,1,1);
run = True
while run:
for event in pygame.event.get():
if event.type == pygame.QUIT:
run = False
pygame.quit()
if not run:
break
glClear(GL_COLOR_BUFFER_BIT)
glUseProgram(shader_program)
glBindVertexArray(VAO)
glDrawArrays(GL_TRIANGLES, 0, 3)
pygame.display.flip()
Result:

PyOpenGL cannot render any vao

I have spent almost 2-3 hours trying to figure out why I am not getting a rectangle renderered. I am using pygame for making a window, and opengl for rendering onto the window.
what should be happening is a red background with a blue rectangle, but what I am seeing is just the empty red background.
this is the code I used below.
am I uploading things to the shader wrong? or did I miss something.
import pygame
from pygame.locals import *
from OpenGL.GL import *
from OpenGL.GLU import *
import numpy as np
print("import sucessfull")
vertex = """
#version 460 core
layout (location=0) in vec3 position;
out vec4 fColor;
void main(){
fColor = vec4(0,1,0,1);
gl_Position = vec4(position, 1.0);
}
"""
fragment = """
#version 460 core
in vec4 fColor;
out vec4 color;
void main(){
color = fColor;
//gl_FragColor = fColor;
}
"""
def main():
clock = pygame.time.Clock()
pygame.init()
display = (800, 600)
pygame.display.set_mode(display, DOUBLEBUF | OPENGL)
gluPerspective(45, (display[0]/display[1]), 0.1, 1000)
glClearColor(1, 0,0,1)
glTranslatef(0,0,-5)
#############################3
vertexID = glCreateShader(GL_VERTEX_SHADER)
glShaderSource(vertexID, vertex)
fragmentID = glCreateShader(GL_FRAGMENT_SHADER)
glShaderSource(fragmentID, fragment)
glCompileShader(vertexID)
glCompileShader(fragmentID)
program = glCreateProgram()
glAttachShader(program, vertexID)
glAttachShader(program, fragmentID)
glLinkProgram(program)
positions = [
0.5, -0.5, 0.0,
-0.5, 0.5, 0.0,
0.5, 0.5, 0.0,
-0.5, -0.5, 0.0
]
indexes = [
2,1,0,
0,1,3
]
vao = glGenVertexArrays(1)
glBindVertexArray(vao)
vbo = glGenBuffers(1)
glBindBuffer(GL_ARRAY_BUFFER, vbo)
glBufferData(GL_ARRAY_BUFFER, np.array(positions, dtype=np.float32), GL_STATIC_DRAW)
ebo = glGenBuffers(1)
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ebo)
glBufferData(GL_ELEMENT_ARRAY_BUFFER, np.array(indexes, dtype=np.float32), GL_STATIC_DRAW)
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 12, 0)
glEnableVertexAttribArray(0)
################################
while True:
for event in pygame.event.get():
if event.type == pygame.QUIT:
pygame.quit()
quit()
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)
#render here
glUseProgram(program)
glBindVertexArray(vao)
glEnableVertexAttribArray(0)
glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0)
glDisableVertexAttribArray(0)
glBindVertexArray(0)
glUseProgram(0)
#***********
pygame.display.flip()
clock.tick(40)
main()
The type of the indices must be integral and correspond to the type specified in the draw call (GL_UNSIGNED_INT). You have to create a NumPy array with the type uint32 instead of float32:
glBufferData(GL_ELEMENT_ARRAY_BUFFER, np.array(indexes, dtype=np.float32), GL_STATIC_DRAW)
glBufferData(GL_ELEMENT_ARRAY_BUFFER, np.array(indexes, dtype=np.uint32), GL_STATIC_DRAW)
If a named buffer object is bound, then the 6th parameter of glVertexAttribPointer is treated as a byte offset into the buffer object's data store. But the type of the parameter is a pointer anyway (c_void_p).
So if the offset is 0, then the 6th parameter can either be None or c_void_p(0) else the offset has to be caste to c_void_p(0):
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 12, 0)
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 12, None)
There is a similar problem with glDrawElements. The last argument is treated as a byte offset:
glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0)
glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, None)
gluPerspective and glTranslatef set the deprecated fixed function pipeline matrices. This instructions do not make any sense when you use "modern" shader program. Remove this instructions. With modern OpenGL you have to use Uniform variables.

How to solve Python OpenGL Memory Leakage problem?

I am trying to draw some traingles and render some texts in screen. But I've observed that memory(RAM) is gradually increasing just only for 6 draw calls. I've 8 GB RAM. When I run the program memory usage goes from 4.2 to 6 within 1 minute. Here is the full code.
from OpenGL.GL import *
from OpenGL.GLU import *
from OpenGL.GL import shaders
from shader import *
import glfw
import freetype
import glm
import numpy as np
from PIL import Image
import math
class CharacterSlot:
def __init__(self, texture, glyph):
self.texture = texture
self.textureSize = (glyph.bitmap.width, glyph.bitmap.rows)
if isinstance(glyph, freetype.GlyphSlot):
self.bearing = (glyph.bitmap_left, glyph.bitmap_top)
self.advance = glyph.advance.x
elif isinstance(glyph, freetype.BitmapGlyph):
self.bearing = (glyph.left, glyph.top)
self.advance = None
else:
raise RuntimeError('unknown glyph type')
def _get_rendering_buffer(xpos, ypos, w, h, zfix=0.0):
return np.asarray([
xpos, ypos + h, 0, 0,
xpos, ypos, 0, 1,
xpos + w, ypos, 1, 1,
xpos, ypos + h, 0, 0,
xpos + w, ypos, 1, 1,
xpos + w, ypos + h, 1, 0
], np.float32)
def init_chars(shaderProgram,window_height,window_width,font_size=24,fontfile = "Vera.ttf"):
glUseProgram(shaderProgram)
#get projection
shader_projection = glGetUniformLocation(shaderProgram, "projection")
W = window_width
H = window_height
projection = glm.ortho(-W/2, W/2, -H/2, H/2)
glUniformMatrix4fv(shader_projection, 1, GL_FALSE, glm.value_ptr(projection))
#disable byte-alignment restriction
glPixelStorei(GL_UNPACK_ALIGNMENT, 1)
face = freetype.Face(fontfile)
face.set_char_size(font_size*64 )
#load first 128 characters of ASCII set
Characters = dict()
for i in range(0,128):
face.load_char(chr(i))
glyph = face.glyph
#generate texture
texture = glGenTextures(1)
glBindTexture(GL_TEXTURE_2D, texture)
glTexImage2D(GL_TEXTURE_2D, 0, GL_RED, glyph.bitmap.width, glyph.bitmap.rows, 0,
GL_RED, GL_UNSIGNED_BYTE, glyph.bitmap.buffer)
#texture options
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE)
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE)
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR)
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR)
#now store character for later use
Characters[chr(i)] = CharacterSlot(texture,glyph)
glBindTexture(GL_TEXTURE_2D, 0)
glUseProgram(0)
return Characters
def render_text(window,shaderProgram,text,x,y,scale,Characters,color=(170,250,255)):
r,g,b = color
glUseProgram(shaderProgram)
#configure VAO/VBO for texture quads
VAO = glGenVertexArrays(1)
glBindVertexArray(VAO)
VBO = glGenBuffers(1)
glBindBuffer(GL_ARRAY_BUFFER, VBO)
glBufferData(GL_ARRAY_BUFFER, 6 * 4 * 4, None, GL_STATIC_DRAW)
glEnableVertexAttribArray(0)
glVertexAttribPointer(0, 4, GL_FLOAT, GL_FALSE, 0, None)
glBindBuffer(GL_ARRAY_BUFFER, 0)
glBindVertexArray(0)
glUniform3f(glGetUniformLocation(shaderProgram, "textColor"),r/255,g/255,b/255)
glActiveTexture(GL_TEXTURE0)
glBindVertexArray(VAO)
for c in text:
ch = Characters[c]
w, h = ch.textureSize
w = w*scale
h = h*scale
vertices = _get_rendering_buffer(x,y,w,h)
#render glyph texture over quad
glBindTexture(GL_TEXTURE_2D, ch.texture)
#update content of VBO memory
glBindBuffer(GL_ARRAY_BUFFER, VBO)
glBufferSubData(GL_ARRAY_BUFFER, 0, vertices.nbytes, vertices)
glBindBuffer(GL_ARRAY_BUFFER, 0)
#render quad
glDrawArrays(GL_TRIANGLES, 0, 6)
#now advance cursors for next glyph (note that advance is number of 1/64 pixels)
x += (ch.advance>>6)*scale
glBindTexture(GL_TEXTURE_2D, 0);
glUseProgram(0)
#UNBIND and DELETE VAO/VBO
glBindVertexArray(0)
glBindBuffer(GL_ARRAY_BUFFER, 0)
glDeleteBuffers(1, id(VBO))
glDeleteBuffers(1, id(VAO))
def triangle(shaderProgram,window,x=0,y=0):
vertices = [-0.5, -0.5, 0.0,
0.5, -0.5, 0.0,
0.0, 0.5, 0.0]
vertices = np.array(vertices, dtype=np.float32)
VBO = glGenBuffers(1)
glBindBuffer(GL_ARRAY_BUFFER, VBO)
glBufferData(GL_ARRAY_BUFFER, vertices.nbytes, vertices, GL_STATIC_DRAW)
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 0, None)
glEnableVertexAttribArray(0)
#use shader program
glUseProgram(shaderProgram)
#accessing ourColor variable from shaderProgram
vertexColorLoc = glGetUniformLocation(shaderProgram, "ourColor")
glUniform4f(vertexColorLoc, 255, 28/255.0, 20/255.0, 0.7);
#transform matrix
transform = glm.mat4(1)
transform = glm.translate(transform,glm.vec3(x,y,0))
MVP = glGetUniformLocation(shaderProgram, "MVP")
glUniformMatrix4fv(MVP, 1, GL_FALSE, glm.value_ptr(transform))
#drawing trangle
glLineWidth(3)
glDrawArrays(GL_TRIANGLES, 0, 3)
glUseProgram(0)
#UNBIND and DELETE VAO/VBO
glBindVertexArray(0)
glBindBuffer(GL_ARRAY_BUFFER, 0)
glDeleteBuffers(1, id(VBO))
def main():
glfw.init()
window = glfw.create_window(640, 640,"EXAMPLE PROGRAM",None,None)
glfw.make_context_current(window)
#initliaze shader programs
shaderProgram = get_shaderProgram()
text_shaderProgram = get_text_shaderProgram()
#load characters and VAO/VBO for text rendering
Characters = init_chars(text_shaderProgram,640,640)
#window loop
while not glfw.window_should_close(window):
glfw.poll_events()
#screen
glClearColor(0, 0, 0, 1)
glClear(GL_COLOR_BUFFER_BIT)
glEnable(GL_BLEND)
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)
#draw functions
render_text(window,text_shaderProgram,"TRIANGLE",-50,-200,1,Characters)
render_text(window,text_shaderProgram,"A",0,180,1,Characters)
render_text(window,text_shaderProgram,"B",-160,-180,1,Characters)
render_text(window,text_shaderProgram,"C",150,-180,1,Characters)
triangle(shaderProgram,window)
triangle(shaderProgram,window,x=0.5,y=0.5)
#swap buffers
glfw.swap_buffers(window)
glfw.swap_interval(1)
glfw.terminate()
if __name__ == '__main__':
main()
The shader program is here. But I think the problems is in buffer object. I've tried to unbind VAO/VBO and delete buffers by following code. But I see no change.
#UNBIND and DELETE VAO/VBO
glBindVertexArray(0)
glBindBuffer(GL_ARRAY_BUFFER, 0)
glDeleteBuffers(1, id(VBO))
glDeleteBuffers(1, id(VAO))
Here is the related problem where accepted answer suggested that glGenBuffers causes memory leak. The alternate function glCreateBuffers is not available in pyopengl. How can I solve this issue?
I can't see any good reason for recreating the Vertex Array Object and Array Buffer Object every time when render_text respectively triangle is called. The vertex specification and the number of vertices doesn't change, so it would be sufficient to update the content of the buffer.
Create the Vertex Array Object and the Array Buffer Object once at initialization:
def init_buffers():
global text_VAO, text_VBO, triangle_VAO, triangle_VBO
text_VAO = glGenVertexArrays(1)
glBindVertexArray(text_VAO)
text_VBO = glGenBuffers(1)
glBindBuffer(GL_ARRAY_BUFFER, text_VBO)
glBufferData(GL_ARRAY_BUFFER, 6 * 4 * 4, None, GL_STATIC_DRAW)
glEnableVertexAttribArray(0)
glVertexAttribPointer(0, 4, GL_FLOAT, GL_FALSE, 0, None)
vertices = [-0.5, -0.5, 0.0,
0.5, -0.5, 0.0,
0.0, 0.5, 0.0]
vertices = np.array(vertices, dtype=np.float32)
triangle_VAO = glGenVertexArrays(1)
glBindVertexArray(triangle_VAO)
triangle_VBO = glGenBuffers(1)
glBindBuffer(GL_ARRAY_BUFFER, triangle_VBO)
glBufferData(GL_ARRAY_BUFFER, vertices.nbytes, vertices, GL_STATIC_DRAW)
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 0, None)
glEnableVertexAttribArray(0)
Use then in the functions render_text and triangle:
def render_text(window,shaderProgram,text,x,y,scale,Characters,color=(170,250,255)):
# [...]
glBindVertexArray(text_VAO)
for c in text:
# [...]
glBindBuffer(GL_ARRAY_BUFFER, text_VBO)
glBufferSubData(GL_ARRAY_BUFFER, 0, vertices.nbytes, vertices)
# glDeleteBuffers(1, id(VBO)) <--- DELETE
# glDeleteBuffers(1, id(VAO)) <--- DELETE
def triangle(shaderProgram,window,x=0,y=0):
glBindVertexArray(triangle_VAO)
# [...]
# glDeleteBuffers(1, id(VBO)) <--- DELETE
Invoke init_buffers before the application loop:
def main():
# [...]
init_buffers()
while not glfw.window_should_close(window):
# [...]

How to render text with PyOpenGL?

I'm learning modern openGL, and at this moment I'm facing trouble with rendering text. I'm following this tutorial which is in C++, but I'm trying to implement in python.
Here is my code:
from OpenGL.GL import *
from OpenGL.GLU import *
from OpenGL.GL import shaders
import glfw
import freetype
import glm
import numpy as np
from PIL import Image
import math
import time
class CharacterSlot:
def __init__(self, texture, glyph):
self.texture = texture
self.textureSize = (glyph.bitmap.width, glyph.bitmap.rows)
if isinstance(glyph, freetype.GlyphSlot):
self.bearing = (glyph.bitmap_left, glyph.bitmap_top)
self.advance = glyph.advance.x
elif isinstance(glyph, freetype.BitmapGlyph):
self.bearing = (glyph.left, glyph.top)
self.advance = None
else:
raise RuntimeError('unknown glyph type')
def _get_rendering_buffer(xpos, ypos, w, h, zfix=0.0):
return np.asarray([
xpos, ypos - h, zfix, 0.0, 1.0,
xpos, ypos, zfix, 0.0, 0.0,
xpos + w, ypos, zfix, 1.0, 0.0,
xpos, ypos - h, zfix, 0.0, 1.0,
xpos + w, ypos, zfix, 1.0, 0.0,
xpos + w, ypos - h, zfix, 1.0, 1.0
], np.float32)
VERTEX_SHADER = """
#version 330 core
layout (location = 0) in vec4 vertex; // <vec2 pos, vec2 tex>
out vec2 TexCoords;
uniform mat4 projection;
void main()
{
gl_Position = projection * vec4(vertex.xy, 0.0, 1.0);
TexCoords = vertex.zw;
}
"""
FRAGMENT_SHADER = """
#version 330 core
in vec2 TexCoords;
out vec4 color;
uniform sampler2D text;
uniform vec3 textColor;
void main()
{
vec4 sampled = vec4(1.0, 1.0, 1.0, texture(text, TexCoords).r);
color = vec4(textColor, 1.0) * sampled;
}
"""
shaderProgram = None
Characters = dict()
VBO = None
VAO = None
def initliaze():
global VERTEXT_SHADER
global FRAGMENT_SHADER
global shaderProgram
global Characters
global VBO
global VAO
#compiling shaders
vertexshader = shaders.compileShader(VERTEX_SHADER, GL_VERTEX_SHADER)
fragmentshader = shaders.compileShader(FRAGMENT_SHADER, GL_FRAGMENT_SHADER)
#creating shaderProgram
shaderProgram = shaders.compileProgram(vertexshader, fragmentshader)
#get projection
#problem
shader_projection = glGetUniformLocation(shaderProgram, "projection")
projection = glm.ortho(0.0,640,0.0,640)
glUniformMatrix4fv(shader_projection, 1, GL_FALSE, glm.value_ptr(projection));
#disable byte-alignment restriction
glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
face = freetype.Face("Vera.ttf")
face.set_char_size( 48*64 )
#load first 128 characters of ASCII set
for i in range(0,128):
face.load_char(chr(i))
glyph = face.glyph
#generate texture
texture = glGenTextures(1)
glBindTexture(GL_TEXTURE_2D, texture)
glTexImage2D(GL_TEXTURE_2D,
0,
GL_RGB,
glyph.bitmap.width, glyph.bitmap.rows,
0,
GL_RGB,
GL_UNSIGNED_BYTE,
glyph.bitmap.buffer)
#texture options
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
#now store character for later use
Characters[chr(i)] = CharacterSlot(texture,glyph)
glBindTexture(GL_TEXTURE_2D, 0);
#configure VAO/VBO for texture quads
VAO = glGenVertexArrays(1)
glBindVertexArray(VAO)
VBO = glGenBuffers(1);
glBindBuffer(GL_ARRAY_BUFFER, VBO);
glBufferData(GL_ARRAY_BUFFER, 6 * 4, None, GL_DYNAMIC_DRAW);
glEnableVertexAttribArray(0);
glVertexAttribPointer(0, 4, GL_FLOAT, GL_FALSE, 4, 0);
glBindBuffer(GL_ARRAY_BUFFER, 0);
glBindVertexArray(0);
def render_text(window,text,x,y,scale,color):
global shaderProgram
global Characters
global VBO
global VAO
face = freetype.Face("Vera.ttf")
face.set_char_size(48*64)
glUniform3f(glGetUniformLocation(shaderProgram, "textColor"),
color[0]/255,color[1]/255,color[2]/255)
glActiveTexture(GL_TEXTURE0);
for c in text:
ch = Characters[c]
w,h = ch.textureSize
w = w*scale
h = w*scale
vertices = _get_rendering_buffer(x,y,w,h)
#render glyph texture over quad
glBindTexture(GL_TEXTURE_2D, ch.texture);
#update content of VBO memory
glBindBuffer(GL_ARRAY_BUFFER, VBO);
glBufferSubData(GL_ARRAY_BUFFER, 0, len(vertices), vertices)
glBindBuffer(GL_ARRAY_BUFFER, 0);
#render quad
glDrawArrays(GL_TRIANGLES, 0, 6);
#now advance cursors for next glyph (note that advance is number of 1/64 pixels)
x += (ch.advance+6)*scale;
glBindVertexArray(0);
glBindTexture(GL_TEXTURE_2D, 0);
glfwSwapBuffers(window);
glfwPollEvents();
def main():
glfw.init()
window = glfw.create_window(640, 640,"EXAMPLE PROGRAM",None,None)
glfw.make_context_current(window)
initliaze()
while not glfw.window_should_close(window):
glfw.poll_events()
glClearColor(0,0,0,1);
glClear(GL_COLOR_BUFFER_BIT);
render_text(window,'hello',1,1,1,(100,100,100))
glfw.terminate()
if __name__ == '__main__':
main()
I'm facing trouble in two portion so far I can understand. The first problem in initliaze(), error raised for the following portion.
shader_projection = glGetUniformLocation(shaderProgram, "projection")
projection = glm.ortho(0.0,640,0.0,640)
glUniformMatrix4fv(shader_projection, 1, GL_FALSE, glm.value_ptr(projection));
I've commented out the above portion to ignore. The second problem is in render_text() function, error raised for the following portion.
glUniform3f(glGetUniformLocation(shaderProgram, "textColor"),
color[0]/255,color[1]/255,color[2]/255)
There could be problems in many more places. I don't understand that why text rendering will be so difficult. What am I missing here?
You missed to install the shader program by glUseProgram:
shaderProgram = shaders.compileProgram(vertexshader, fragmentshader)
glUseProgram(shaderProgram) # <---
The 2nd argument to glBufferData and the 3rd argument of glBufferSubData is the size in bytes:
glBufferData(GL_ARRAY_BUFFER, 6 * 4, None, GL_DYNAMIC_DRAW)
glBufferData(GL_ARRAY_BUFFER, 6 * 4 * 4, None, GL_DYNAMIC_DRAW)
glBufferSubData(GL_ARRAY_BUFFER, 0, len(vertices), vertices)
glBufferSubData(GL_ARRAY_BUFFER, 0, vertices.nbytes, vertices)
The vertex attribute consist of a 2 dimension vertex coordinate (x, y) and a 2 dimensional texture coordinate. Remove the wird zfix from the array of vertex attribute data. Furthermore you have to flip the 2nd component of the texture coordinates (otherwise the text is upside down)
def _get_rendering_buffer(xpos, ypos, w, h, zfix=0.0):
return np.asarray([
xpos, ypos - h, 0, 0,
xpos, ypos, 0, 1,
xpos + w, ypos, 1, 1,
xpos, ypos - h, 0, 0,
xpos + w, ypos, 1, 1,
xpos + w, ypos - h, 1, 0
], np.float32)
The stride argument of glVertexAttribIPointer has to be specified in bytes. If stride is 0, the generic vertex attributes are understood to be tightly packed in the array. Hence in your case stride has to be 16 or 0:
glVertexAttribPointer(0, 4, GL_FLOAT, GL_FALSE, 4, 0)
glVertexAttribPointer(0, 4, GL_FLOAT, GL_FALSE, 0, None)
face.load_char(chr(i)) generates a image with on color channel (1 byte per pixel). Use the internal format and format GL_RED rather than GL_RGB for generating the 2 dimensional texture image:
glTexImage2D(GL_TEXTURE_2D, 0, GL_RED, glyph.bitmap.width, glyph.bitmap.rows, 0,
GL_RED, GL_UNSIGNED_BYTE, glyph.bitmap.buffer)
You have to bind the vertex array, before drawing the text:
glBindVertexArray(VAO)
for c in text:
# [...]
glDrawArrays(GL_TRIANGLES, 0, 6)
There is typo when you increment x, you have to use the >>-operator rather than the +-operator:
x += (ch.advance+6)*scale
x += (ch.advance>>6)*scale
and another typo when you compute h:
h = w*scale
h = h*scale
You have to enable alpha blending:
glEnable(GL_BLEND)
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)
In NDC (normalized device coordinates) the left bottom is (-1, -1) and the right top is (1, 1). Set the orthographic projection in that way, that the top left of the window is at (0, 0):
projection = glm.ortho(0.0,640,0.0,640)
projection = glm.ortho(0, 640, 640, 0)
The reference point of the text is at the bottom. Hence you have to set a x coordinate greater than the text height:
render_text(window,'hello',1,1,1,(100,100,100))
render_text(window,'hello', 20, 50, 1, (255, 100, 100))
See the complete example (I've used a different font):
from OpenGL.GL import *
from OpenGL.GLU import *
from OpenGL.GL import shaders
import glfw
import freetype
import glm
import numpy as np
from PIL import Image
import math
import time
fontfile = "Vera.ttf"
#fontfile = r'C:\source\resource\fonts\gnu-freefont_freesans\freesans.ttf'
class CharacterSlot:
def __init__(self, texture, glyph):
self.texture = texture
self.textureSize = (glyph.bitmap.width, glyph.bitmap.rows)
if isinstance(glyph, freetype.GlyphSlot):
self.bearing = (glyph.bitmap_left, glyph.bitmap_top)
self.advance = glyph.advance.x
elif isinstance(glyph, freetype.BitmapGlyph):
self.bearing = (glyph.left, glyph.top)
self.advance = None
else:
raise RuntimeError('unknown glyph type')
def _get_rendering_buffer(xpos, ypos, w, h, zfix=0.0):
return np.asarray([
xpos, ypos - h, 0, 0,
xpos, ypos, 0, 1,
xpos + w, ypos, 1, 1,
xpos, ypos - h, 0, 0,
xpos + w, ypos, 1, 1,
xpos + w, ypos - h, 1, 0
], np.float32)
VERTEX_SHADER = """
#version 330 core
layout (location = 0) in vec4 vertex; // <vec2 pos, vec2 tex>
out vec2 TexCoords;
uniform mat4 projection;
void main()
{
gl_Position = projection * vec4(vertex.xy, 0.0, 1.0);
TexCoords = vertex.zw;
}
"""
FRAGMENT_SHADER = """
#version 330 core
in vec2 TexCoords;
out vec4 color;
uniform sampler2D text;
uniform vec3 textColor;
void main()
{
vec4 sampled = vec4(1.0, 1.0, 1.0, texture(text, TexCoords).r);
color = vec4(textColor, 1.0) * sampled;
}
"""
shaderProgram = None
Characters = dict()
VBO = None
VAO = None
def initliaze():
global VERTEXT_SHADER
global FRAGMENT_SHADER
global shaderProgram
global Characters
global VBO
global VAO
#compiling shaders
vertexshader = shaders.compileShader(VERTEX_SHADER, GL_VERTEX_SHADER)
fragmentshader = shaders.compileShader(FRAGMENT_SHADER, GL_FRAGMENT_SHADER)
#creating shaderProgram
shaderProgram = shaders.compileProgram(vertexshader, fragmentshader)
glUseProgram(shaderProgram)
#get projection
#problem
shader_projection = glGetUniformLocation(shaderProgram, "projection")
projection = glm.ortho(0, 640, 640, 0)
glUniformMatrix4fv(shader_projection, 1, GL_FALSE, glm.value_ptr(projection))
#disable byte-alignment restriction
glPixelStorei(GL_UNPACK_ALIGNMENT, 1)
face = freetype.Face(fontfile)
face.set_char_size( 48*64 )
#load first 128 characters of ASCII set
for i in range(0,128):
face.load_char(chr(i))
glyph = face.glyph
#generate texture
texture = glGenTextures(1)
glBindTexture(GL_TEXTURE_2D, texture)
glTexImage2D(GL_TEXTURE_2D, 0, GL_RED, glyph.bitmap.width, glyph.bitmap.rows, 0,
GL_RED, GL_UNSIGNED_BYTE, glyph.bitmap.buffer)
#texture options
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE)
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE)
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR)
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR)
#now store character for later use
Characters[chr(i)] = CharacterSlot(texture,glyph)
glBindTexture(GL_TEXTURE_2D, 0)
#configure VAO/VBO for texture quads
VAO = glGenVertexArrays(1)
glBindVertexArray(VAO)
VBO = glGenBuffers(1)
glBindBuffer(GL_ARRAY_BUFFER, VBO)
glBufferData(GL_ARRAY_BUFFER, 6 * 4 * 4, None, GL_DYNAMIC_DRAW)
glEnableVertexAttribArray(0)
glVertexAttribPointer(0, 4, GL_FLOAT, GL_FALSE, 0, None)
glBindBuffer(GL_ARRAY_BUFFER, 0)
glBindVertexArray(0)
def render_text(window,text,x,y,scale,color):
global shaderProgram
global Characters
global VBO
global VAO
face = freetype.Face(fontfile)
face.set_char_size(48*64)
glUniform3f(glGetUniformLocation(shaderProgram, "textColor"),
color[0]/255,color[1]/255,color[2]/255)
glActiveTexture(GL_TEXTURE0)
glEnable(GL_BLEND)
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)
glBindVertexArray(VAO)
for c in text:
ch = Characters[c]
w, h = ch.textureSize
w = w*scale
h = h*scale
vertices = _get_rendering_buffer(x,y,w,h)
#render glyph texture over quad
glBindTexture(GL_TEXTURE_2D, ch.texture)
#update content of VBO memory
glBindBuffer(GL_ARRAY_BUFFER, VBO)
glBufferSubData(GL_ARRAY_BUFFER, 0, vertices.nbytes, vertices)
glBindBuffer(GL_ARRAY_BUFFER, 0)
#render quad
glDrawArrays(GL_TRIANGLES, 0, 6)
#now advance cursors for next glyph (note that advance is number of 1/64 pixels)
x += (ch.advance>>6)*scale
glBindVertexArray(0)
glBindTexture(GL_TEXTURE_2D, 0)
glfw.swap_buffers(window)
glfw.poll_events()
def main():
glfw.init()
window = glfw.create_window(640, 640,"EXAMPLE PROGRAM",None,None)
glfw.make_context_current(window)
initliaze()
while not glfw.window_should_close(window):
glfw.poll_events()
glClearColor(0,0,0,1)
glClear(GL_COLOR_BUFFER_BIT)
render_text(window,'hello', 20, 50, 1, (255, 100, 100))
glfw.terminate()
if __name__ == '__main__':
main()
See also FreeType / OpenGL text rendering

Rendered model disappears at some asymptote

I've been trying to render a simple teapot with PyOpenGL, but have been running into strange issues. I can't seem to figure out exactly where the error originates from, despite the simplicity of the code.
Main.py
import pygame
from pygame.locals import *
from MV import *
import ctypes
from OpenGL.GL import *
from OpenGL.GL import shaders
from OpenGL.GLU import *
import teapot as tp
vertex_shader = '''
#version 420
in vec3 vpos_modelspace;
in vec3 vnorm_modelspace;
uniform mat4 mvp;
out vec4 vertcolor;
void main(){
vertcolor = vec4(vnorm_modelspace, 1.0);
gl_Position = mvp * vec4(vpos_modelspace, 1.0);
}
'''
fragment_shader = '''
#version 420
in vec4 vertcolor;
out vec4 fragcolor;
void main(){
fragcolor = vertcolor;
}
'''
model = tp.teapot
pygame.init()
canvas = pygame.display.set_mode((800, 600), DOUBLEBUF|OPENGL)
pygame.display.set_caption('Test')
glClearColor(.5, .5, .5, 1)
glEnable(GL_DEPTH_TEST)
glDepthFunc(GL_LESS)
glDisable(GL_CULL_FACE)
VERTEXSHADER = shaders.compileShader(vertex_shader, GL_VERTEX_SHADER)
FRAGMENTSHADER = shaders.compileShader(fragment_shader, GL_FRAGMENT_SHADER)
program = shaders.compileProgram(VERTEXSHADER, FRAGMENTSHADER)
glUseProgram(program)
vpos_loc = glGetAttribLocation(program, 'vpos_modelspace')
vnorm_loc = glGetAttribLocation(program, 'vnorm_modelspace')
mvp_loc = glGetUniformLocation(program, 'mvp')
eye = numpy.array([0, 0, 1], dtype=numpy.float32)
at = numpy.array([0, 0, 0], dtype=numpy.float32)
up = numpy.array([0, 1, 0], dtype=numpy.float32)
mvp = frustum(-1, 1, 1, -1, .1, 1000)#lookAt(eye, at, up)
vao = glGenVertexArrays(1)
glBindVertexArray(vao)
vbo_pos = glGenBuffers(1)
glBindBuffer(GL_ARRAY_BUFFER, vbo_pos)
vbo_norm = glGenBuffers(1)
glBindBuffer(GL_ARRAY_BUFFER, vbo_norm)
verts = []
normals = []
for i in range(0, len(model.faces), 3):
index = model.faces[i:i+3]
verts.extend(model.vertices[3*index[0]:3*index[0]+3])
verts.extend(model.vertices[3*index[1]:3*index[1]+3])
verts.extend(model.vertices[3*index[2]:3*index[2]+3])
normals.extend(model.normals[3*index[0]:3*index[0]+3])
normals.extend(model.normals[3*index[1]:3*index[1]+3])
normals.extend(model.normals[3*index[2]:3*index[2]+3])
verts = numpy.array(verts, dtype=numpy.float32)
normals = numpy.array(normals, dtype=numpy.float32)
glBindBuffer(GL_ARRAY_BUFFER, vbo_pos)
glBufferData(GL_ARRAY_BUFFER, verts.size * verts.itemsize, verts, GL_STATIC_DRAW)
glVertexAttribPointer(vpos_loc, 3, GL_FLOAT, GL_FALSE, 0, ctypes.c_void_p(0))
glEnableVertexAttribArray(vpos_loc)
glBindBuffer(GL_ARRAY_BUFFER, vbo_norm)
glBufferData(GL_ARRAY_BUFFER, normals.size * normals.itemsize, normals, GL_STATIC_DRAW)
glVertexAttribPointer(vnorm_loc, 3, GL_FLOAT, GL_FALSE, 0, ctypes.c_void_p(0))
glEnableVertexAttribArray(vnorm_loc)
glBindBuffer(GL_ARRAY_BUFFER, 0)
glBindVertexArray(0)
while(True):
for event in pygame.event.get():
if event.type == pygame.QUIT:
pygame.quit()
quit()
glUseProgram(program)
rotation_matrix = rotate(.01, [0, 1, 0])
mvp = mvp # rotation_matrix
glUniformMatrix4fv(mvp_loc, 1, GL_FALSE, mvp.flatten())
glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT)
glBindVertexArray(vao)
glDrawArrays(GL_TRIANGLES, 0, int(verts.size/3))
glBindVertexArray(0)
glUseProgram(0)
pygame.display.flip()
main()
MV.py
import numpy
def normalize(vector):
return vector/numpy.linalg.norm(vector)
def translate(pos):
return numpy.array([[1, 0, 0, pos[0]],
[0, 1, 0, pos[1]],
[0, 0, 1, pos[2]],
[0, 0, 0, 1]], dtype=numpy.float32)
def rotate(angle, axis):
rads = angle * numpy.pi/180
v = normalize(axis)
c = numpy.cos(rads)
omc = 1-c
s = numpy.sin(rads)
return numpy.array([[v[0]*v[0]*omc + c, v[0]*v[1]*omc - v[2]*s, v[0]*v[2]*omc + v[1]*s, 0],
[v[0]*v[1]*omc + v[2]*s, v[1]*v[1]*omc + c, v[1]*v[2]*omc - v[0]*s, 0],
[v[0]*v[2]*omc - v[1]*s, v[1]*v[2]*omc + v[0]*s, v[2]*v[2]*omc + c, 0],
[0, 0, 0, 1]], dtype=numpy.float32)
def lookAt(eye, at, up):
n = normalize(at-eye)
u = normalize(numpy.cross(n, up))
v = normalize(numpy.cross(u, n))
rotate = numpy.array([[u[0], v[0], -n[0], 0],
[u[1], v[1], -n[1], 0],
[u[2], v[2], -n[2], 0],
[0, 0, 0, 1]], dtype=numpy.float32).transpose()
return rotate#translate(-eye)
def frustum(left, right, top, bottom, near, far):
rl = right-left
tb = top-bottom
fn = far-near
return numpy.array([[2*near/rl, 0, (right+left)/rl, 0],
[0, 2*near/tb, (top+bottom)/tb, 0],
[0, 0, -(far+near)/fn, -(2*far*near)/fn],
[0, 0, -1, 0]], dtype=numpy.float32)
The output shows the teapot being rotated (though not about the axis that I expected) and sort of shrinking and disappearing at rotations of 0, pi, 2pi, etc. I believe the teapot vertices are being processed correctly, as it does show up when rotated and is correctly shaded with normal values.
Output at 5 degrees - Model is 'growing'
Output at 30 degrees - Strange culling?
Output at 60 degrees - Relatively normal
Output at 170 degrees - Model is 'shrinking'
Output at 190 degrees - Model is 'growing' on the other side of the plane
At rotations 0, pi, 2pi, etc the model is completely invisible/too small to see.

Categories