I'm really new to this community. I'm sorry for any mistakes in advance.I'm making a game like minecraft with GLFW and OpenGL. The problem is, it just renders three faces correctly and the other faces have a wierd glitch. Here is my code:
main.py
# imports
import glfw
from OpenGL.GL import *
from OpenGL.GLU import *
# internal imports
from core.renderer import *
from player import *
if not glfw.init():
raise Exception("glfw can not be initialized!")
window = glfw.create_window(800, 500, "PyCraft", None, None)
glfw.make_context_current(window)
renderer = TerrainRenderer(window)
player = Player(window)
renderer.texture_manager.add_from_folder("assets/textures/block/")
renderer.texture_manager.save("atlas.png")
renderer.texture_manager.bind()
glEnable(GL_DEPTH_TEST)
glEnable(GL_CULL_FACE)
glCullFace(GL_BACK)
glEnable(GL_FOG)
glFogfv(GL_FOG_COLOR, (GLfloat * int(8))(0.5, 0.69, 1.0, 10))
glHint(GL_FOG_HINT, GL_DONT_CARE)
glFogi(GL_FOG_MODE, GL_LINEAR)
glFogf(GL_FOG_START, 30)
glFogf(GL_FOG_END, 100)
# get window size
def get_window_size():
width, height = glfw.get_window_size(window)
return width, height
def _setup_3d():
w, h = get_window_size()
glMatrixMode(GL_PROJECTION)
glLoadIdentity()
gluPerspective(70, w / h, 0.1, 1000)
glMatrixMode(GL_MODELVIEW)
glLoadIdentity()
def _update_3d():
_setup_3d()
glViewport(0, 0, *get_window_size())
def add_cube(x, y, z):
X, Y, Z = x + 1, y + 1, z + 1
renderer.add((x, Y, Z, X, Y, Z, X, Y, z, x, Y, z), renderer.texture_manager.get_texture("grass"))
renderer.add((x, y, z, X, y, z, X, y, Z, x, y, Z), renderer.texture_manager.get_texture("grass"))
renderer.add((x, y, z, x, y, Z, x, Y, Z, x, Y, z), renderer.texture_manager.get_texture("grass"))
renderer.add((X, y, Z, X, y, z, X, Y, z, X, Y, Z), renderer.texture_manager.get_texture("grass"))
renderer.add((x, y, Z, X, y, Z, X, Y, Z, x, Y, Z), renderer.texture_manager.get_texture("grass"))
renderer.add((X, y, z, x, y, z, x, Y, z, X, Y, z), renderer.texture_manager.get_texture("grass"))
add_cube(0, 0, -2)
# mainloop
while not glfw.window_should_close(window):
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)
_update_3d()
glClearColor(0.5, 0.7, 1, 1.0)
player.update()
renderer.render()
glfw.poll_events()
glfw.swap_buffers(window)
glfw.terminate()
renderer.py:
# imports
import glfw
from OpenGL.GL import *
from ctypes import *
from core.texture_manager import *
import threading
import numpy as np
from core.logger import *
glfw.init()
class TerrainRenderer:
def __init__(self, window):
self.event = threading.Event()
self.to_add = []
self._len = 0
self.parent = window
self.vertices = []
self.texCoords = []
self.create_vbo(window)
self.texture_manager = TextureAtlas()
glEnable(GL_TEXTURE_2D)
glEnable(GL_BLEND)
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)
glEnableClientState(GL_TEXTURE_COORD_ARRAY)
glEnableClientState (GL_VERTEX_ARRAY)
def shared_context(self, window):
glfw.window_hint(glfw.VISIBLE, glfw.FALSE)
window2 = glfw.create_window(500,500, "Window 2", None, window)
glfw.make_context_current(window2)
self.event.set()
while not glfw.window_should_close(window):
if len(self.to_add) > 0:
i = self.to_add.pop(0)
vertices = np.array(i[0], dtype=np.float32)
texture_coords = np.array(i[1], dtype=np.float32)
bytes_vertices = vertices.nbytes
bytes_texCoords = texture_coords.nbytes
verts = (GLfloat * len(vertices))(*vertices)
texCoords = (GLfloat * len(texture_coords))(*texture_coords)
log_vertex_addition((vertices, texture_coords), (bytes_vertices, bytes_texCoords), self._len*4, len(self.to_add))
glBindBuffer(GL_ARRAY_BUFFER, self.vbo)
glBufferSubData(GL_ARRAY_BUFFER, self._len, bytes_vertices, verts)
glVertexPointer (3, GL_FLOAT, 0, None)
glFlush()
glBindBuffer(GL_ARRAY_BUFFER, self.vbo_1)
glBufferSubData(GL_ARRAY_BUFFER, self._len, bytes_texCoords, texCoords)
glTexCoordPointer(2, GL_FLOAT, 0, None)
glFlush()
glVertexPointer(3, GL_FLOAT, 0, None)
glTexCoordPointer(3, GL_FLOAT, 0, None)
self.vertices += i[0]
self.texCoords += i[1]
self._len += bytes_vertices
glfw.poll_events()
glfw.swap_buffers(window2)
glfw.terminate()
def create_vbo(self, window):
self.vbo, self.vbo_1 = glGenBuffers (2)
glBindBuffer(GL_ARRAY_BUFFER, self.vbo)
glBufferData(GL_ARRAY_BUFFER, 64000000, None, GL_STATIC_DRAW)
glBindBuffer(GL_ARRAY_BUFFER, self.vbo_1)
glBufferData(GL_ARRAY_BUFFER, 64000000, None, GL_STATIC_DRAW)
glfw.make_context_current(None)
thread = threading.Thread(target=self.shared_context, args=[window], daemon=True)
thread.start()
self.event.wait()
glfw.make_context_current(window)
def add(self, vertices, texCoords):
self.to_add.append((tuple(vertices), tuple(texCoords)))
def render(self):
glClear (GL_COLOR_BUFFER_BIT)
glEnable(GL_TEXTURE_2D)
glEnable(GL_BLEND)
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)
glBindBuffer (GL_ARRAY_BUFFER, self.vbo)
glVertexPointer (3, GL_FLOAT, 0, None)
glBindBuffer(GL_ARRAY_BUFFER, self.vbo_1)
glTexCoordPointer(2, GL_FLOAT, 0, None)
glDrawArrays (GL_QUADS, 0, self._len)
glDisable(GL_TEXTURE_2D)
glDisable(GL_BLEND)
Output
It is expected that all faces of the above cube are lime.
Right now, it shows no errors, but it does not render the cube properly. The attached GIF explains what I mean.
When I use this code in renderer.py, it works just fine!
# imports
import glfw, numpy
from OpenGL.GL import *
from ctypes import *
from core.texture_manager import *
glfw.init()
class VBOManager:
def __init__(self, renderer):
self.renderer = renderer
self.run()
def run(self):
for i in self.renderer.to_add[:self.renderer.to_add_count]:
self.renderer.vertices.extend(i[0])
self.renderer.texCoords.extend(i[1])
self.renderer.to_add.remove(i)
glBindBuffer(GL_ARRAY_BUFFER, self.renderer.vbo)
glBufferData(GL_ARRAY_BUFFER, len(self.renderer.vertices) * 4, (c_float * len(self.renderer.vertices))(*self.renderer.vertices), GL_STATIC_DRAW)
glFlush()
glVertexPointer(3, GL_FLOAT, 0, None)
glTexCoordPointer(3, GL_FLOAT, 0, None)
glBindBuffer(GL_ARRAY_BUFFER, self.renderer.vbo_1)
glBufferData(GL_ARRAY_BUFFER, len(self.renderer.texCoords) * 4, (c_float * len(self.renderer.texCoords))(*self.renderer.texCoords), GL_STATIC_DRAW)
glFlush()
class TerrainRenderer:
def __init__(self, window):
self.window = window
self.vertices = []
self.texCoords = []
self.to_add = []
self.to_add_count = 256
self.vbo, self.vbo_1 = glGenBuffers (2)
glBindBuffer(GL_ARRAY_BUFFER, self.vbo)
glBufferData(GL_ARRAY_BUFFER, 12 * 4, None, GL_STATIC_DRAW)
self.vbo_manager = VBOManager(self)
self.texture_manager = TextureAtlas()
glEnable(GL_TEXTURE_2D)
glEnable(GL_BLEND)
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)
glEnableClientState(GL_TEXTURE_COORD_ARRAY)
glEnableClientState (GL_VERTEX_ARRAY)
def render(self):
try:
self.vbo_manager.run()
except RuntimeError:
pass
glClear (GL_COLOR_BUFFER_BIT)
glEnable(GL_TEXTURE_2D)
glEnable(GL_BLEND)
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)
glBindBuffer (GL_ARRAY_BUFFER, self.vbo)
glVertexPointer (3, GL_FLOAT, 0, None)
glBindBuffer(GL_ARRAY_BUFFER, self.vbo_1)
glTexCoordPointer(2, GL_FLOAT, 0, None)
glDrawArrays (GL_QUADS, 0, len(self.vertices))
glDisable(GL_TEXTURE_2D)
glDisable(GL_BLEND)
def add(self, posList, texCoords):
self.to_add.append((numpy.array(posList), numpy.array(texCoords)))
def update_vbo(self):
pass
Why does this code work and not the previous one? Have I missed something?
Any help will be highly appreciated.
The solution
I noticed that texCoords have two elements per vertex and vertices have three. So I created a self._len_ variable in the TerrainRenderer class to count the texCoord length separately. Here is my code for TerrainRenderer:
class TerrainRenderer:
def __init__(self, window):
self.event = threading.Event()
self.to_add = []
self._len = 0
self._len_ = 0 # CHANGED
self.parent = window
self.vertices = []
self.texCoords = []
self.create_vbo(window)
self.texture_manager = TextureAtlas()
glEnable(GL_TEXTURE_2D)
glEnable(GL_BLEND)
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)
if not USING_RENDERDOC:
glEnableClientState(GL_VERTEX_ARRAY)
glEnableClientState(GL_TEXTURE_COORD_ARRAY)
def shared_context(self, window):
glfw.window_hint(glfw.VISIBLE, glfw.FALSE)
window2 = glfw.create_window(500,500, "Window 2", None, window)
glfw.make_context_current(window2)
self.event.set()
while not glfw.window_should_close(window):
if len(self.to_add) > 0:
i = self.to_add.pop(0)
vertices = np.array(i[0], dtype=np.float32)
texture_coords = np.array(i[1], dtype=np.float32)
bytes_vertices = vertices.nbytes
bytes_texCoords = texture_coords.nbytes
verts = (GLfloat * len(vertices))(*vertices)
texCoords = (GLfloat * len(texture_coords))(*texture_coords)
log_vertex_addition((vertices, texture_coords), (bytes_vertices, bytes_texCoords), self._len*4, len(self.to_add))
glBindBuffer(GL_ARRAY_BUFFER, self.vbo)
glBufferSubData(GL_ARRAY_BUFFER, self._len, bytes_vertices, verts)
if not USING_RENDERDOC:
glVertexPointer (3, GL_FLOAT, 0, None)
glFlush()
glBindBuffer(GL_ARRAY_BUFFER, self.vbo_1)
glBufferSubData(GL_ARRAY_BUFFER, self._len_, bytes_texCoords, texCoords) # CHANGED
if not USING_RENDERDOC:
glTexCoordPointer(2, GL_FLOAT, 0, None)
glFlush()
self.vertices += i[0]
self.texCoords += i[1]
self._len += bytes_vertices
self._len_ += bytes_texCoords # CHANGED
glfw.poll_events()
glfw.swap_buffers(window2)
glfw.terminate()
def create_vbo(self, window):
self.vbo, self.vbo_1 = glGenBuffers (2)
glBindBuffer(GL_ARRAY_BUFFER, self.vbo)
glBufferData(GL_ARRAY_BUFFER, 64000000, None, GL_STATIC_DRAW)
glBindBuffer(GL_ARRAY_BUFFER, self.vbo_1)
glBufferData(GL_ARRAY_BUFFER, 64000000, None, GL_STATIC_DRAW)
glfw.make_context_current(None)
thread = threading.Thread(target=self.shared_context, args=[window], daemon=True)
thread.start()
self.event.wait()
glfw.make_context_current(window)
def add(self, vertices, texCoords):
self.to_add.append((tuple(vertices), tuple(texCoords)))
def render(self):
glClear (GL_COLOR_BUFFER_BIT)
glEnable(GL_TEXTURE_2D)
glEnable(GL_BLEND)
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)
glBindBuffer (GL_ARRAY_BUFFER, self.vbo)
if not USING_RENDERDOC:
glVertexPointer (3, GL_FLOAT, 0, None)
glBindBuffer(GL_ARRAY_BUFFER, self.vbo_1)
if not USING_RENDERDOC:
glTexCoordPointer(2, GL_FLOAT, 0, None)
glDrawArrays (GL_QUADS, 0, self._len)
glDisable(GL_TEXTURE_2D)
glDisable(GL_BLEND)
Related
I'm making a basic voxel rendering class with python using glfw. I'm using glBufferSubData instead of glBufferData, but it won't render. Here is the code that is suspected to be causing the problem:
# imports
import glfw, numpy as np
from OpenGL.GL import *
from ctypes import *
from core.texture_manager import *
glfw.init()
class VBOManager:
def __init__(self, renderer):
self.renderer = renderer
self.run()
def run(self):
for i in self.renderer.to_add[:self.renderer.to_add_count]:
self.renderer.vertices.extend(i[0])
self.renderer.texCoords.extend(i[1])
vertices = np.array(i[0], dtype=np.float32)
texCoords = np.array(i[1], dtype=np.float32)
# use glBufferSubData
glBindBuffer(GL_ARRAY_BUFFER, self.renderer.vbo)
glBufferData(GL_ARRAY_BUFFER, self.renderer.vertices.nbytes, vertices.nbytes, (c_float * len(vertices))(*vertices))
glFlush()
glVertexPointer(3, GL_FLOAT, 0, None)
glTexCoordPointer(3, GL_FLOAT, 0, None)
glBindBuffer(GL_ARRAY_BUFFER, self.renderer.vbo_1)
glBufferSubData(GL_ARRAY_BUFFER, self.renderer.texCoords.nbytes, texCoords.nbytes, (c_float * len(texCoords))(*texCoords))
glFlush()
self.renderer.to_add.remove(i)
class TerrainRenderer:
def __init__(self, window):
self.window = window
self.vertices = np.array([])
self.texCoords = np.array([])
self.to_add = []
self.to_add_count = 256
self.vbo, self.vbo_1 = glGenBuffers (2)
glBindBuffer(GL_ARRAY_BUFFER, self.vbo)
glBufferData(GL_ARRAY_BUFFER, 12 * 4, None, GL_STATIC_DRAW)
self.vbo_manager = VBOManager(self)
self.texture_manager = TextureAtlas()
glEnable(GL_TEXTURE_2D)
glEnable(GL_BLEND)
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)
glEnableClientState(GL_TEXTURE_COORD_ARRAY)
glEnableClientState (GL_VERTEX_ARRAY)
def render(self):
try:
self.vbo_manager.run()
except RuntimeError:
pass
glClear (GL_COLOR_BUFFER_BIT)
glEnable(GL_TEXTURE_2D)
glEnable(GL_BLEND)
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)
glBindBuffer (GL_ARRAY_BUFFER, self.vbo)
glVertexPointer (3, GL_FLOAT, 0, None)
glBindBuffer(GL_ARRAY_BUFFER, self.vbo_1)
glTexCoordPointer(2, GL_FLOAT, 0, None)
glDrawArrays (GL_QUADS, 0, len(self.vertices))
glDisable(GL_TEXTURE_2D)
glDisable(GL_BLEND)
def add(self, posList, texCoords):
self.to_add.append((numpy.array(posList), numpy.array(texCoords)))
When I start the application that uses this code, it shows no logs, warnings or errors, but it also doesn't render the vertices at all. NOTHING is visible on the screen.
glBufferSubData just updates a buffer object's data store but doesn't creates a buffer object's data store. You need to create the buffer object's data store with glBufferData, after that you can update the data with glBufferSubData. Furthermore, The 2nd argument of glBufferSubData specifies the offset into the buffer object's data store where data replacement will begin.
glBindBuffer(GL_ARRAY_BUFFER, self.vbo_1)
glBufferData(GL_ARRAY_BUFFER, texCoords.nbytes, None, GL_STATIC_DRAW)
glBindBuffer(GL_ARRAY_BUFFER, self.renderer.vbo_1)
glBufferSubData(GL_ARRAY_BUFFER, 0, texCoords.nbytes, (c_float * len(texCoords))(*texCoords))
code:
import glfw
import numpy as np
from OpenGL.GL import *
def main():
if not glfw.init():
raise RuntimeError('Can not initialize glfw library')
window = glfw.create_window(500, 500, 'Demo', None, None)
if not window:
glfw.terminate()
raise RuntimeError('Can not create glfw window')
glfw.make_context_current(window)
glClearColor(0, 0, 0, 1)
glColor(1, 0, 0, 1)
glPointSize(10.0)
VBO = glGenBuffers(1)
glBindBuffer(GL_ARRAY_BUFFER, VBO)
# The result of following two lines are looks the same
# glBufferData(GL_ARRAY_BUFFER, np.array([0, 0, 0], dtype='float32'), GL_STATIC_DRAW)
glBufferData(GL_ARRAY_BUFFER, np.array([999999999, 999999999, 999999999], dtype='float32'), GL_STATIC_DRAW)
while not glfw.window_should_close(window):
glClear(GL_COLOR_BUFFER_BIT)
glEnableVertexAttribArray(0)
glBindBuffer(GL_ARRAY_BUFFER, VBO)
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 0, 0)
glDrawArrays(GL_POINTS, 0, 1)
glDisableVertexAttribArray(0)
glfw.swap_buffers(window)
glfw.poll_events()
glfw.terminate()
if __name__ == "__main__":
main()
I'm studying OpenGL and I'm trying to follow the tutorial here. However, I found the position of the point never change even if I change the data in "glBufferData".
I don't known how this happened. Is the function glBufferData not working? Or maybe I made some low-level mistakes.
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. However the type of the parameter is c_void_p.
Therfore 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, 0, 0)
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 0, None)
Minimal example:
import glfw
import numpy as np
from OpenGL.GL import *
def main():
if not glfw.init():
raise RuntimeError('Can not initialize glfw library')
window = glfw.create_window(500, 500, 'Demo', None, None)
if not window:
glfw.terminate()
raise RuntimeError('Can not create glfw window')
glfw.make_context_current(window)
glClearColor(0, 0, 0, 1)
glColor(1, 0, 0, 1)
glPointSize(10.0)
VBO = glGenBuffers(1)
glBindBuffer(GL_ARRAY_BUFFER, VBO)
glBufferData(GL_ARRAY_BUFFER, np.array([0.2, -0.2, 0, -0.2, -0.2, 0, 0, 0.2, 0], dtype='float32'), GL_STATIC_DRAW)
while not glfw.window_should_close(window):
glClear(GL_COLOR_BUFFER_BIT)
glEnableVertexAttribArray(0)
glBindBuffer(GL_ARRAY_BUFFER, VBO)
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 0, None)
glDrawArrays(GL_POINTS, 0, 3)
glDisableVertexAttribArray(0)
glfw.swap_buffers(window)
glfw.poll_events()
glfw.terminate()
if __name__ == "__main__":
main()
I am trying to render multiple objects in pyopengl using pyqt5. After following tutorials I created a 3D mesh which uploads a wavefront obj file and renders it with texture. This worked for me:
class Model:
def __init__(self, file_name, texture_name):
self.object = ObjectLoader()
self.object.load_model(file_name)
//creating and compiling shaders
glUseProgram(shader)
vertex_buffer_object = GLuint(0)
glGenBuffers(1, vertex_buffer_object)
glBindBuffer(GL_ARRAY_BUFFER, vertex_buffer_object)
glBufferData(GL_ARRAY_BUFFER, len(self.object.model) * 4, self.object.c_model, GL_DYNAMIC_DRAW)
glDrawArrays(GL_TRIANGLES, 0, len(self.object.vertex_index))
# vertices
vertex_offset = 0
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, self.object.model.itemsize * 3, ctypes.c_void_p(vertex_offset))
glEnableVertexAttribArray(0)
# textures
texture_offset = len(self.object.vertex_index) * 12
glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, self.object.model.itemsize * 2, ctypes.c_void_p(texture_offset))
glEnableVertexAttribArray(1)
# normals
normal_offset = texture_offset + len(self.object.texture_index) * 8
glVertexAttribPointer(2, 3, GL_FLOAT, GL_FALSE, self.object.model.itemsize * 3, ctypes.c_void_p(normal_offset))
glEnableVertexAttribArray(2)
texture = GLuint(0)
glGenTextures(1, texture)
glBindTexture(GL_TEXTURE_2D, texture)
# texture wrapping
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT)
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT)
# texture filtering
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST)
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST)
glEnable(GL_BLEND)
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)
image = Image.open(texture_name)
flipped_image = image.transpose(Image.FLIP_TOP_BOTTOM)
image_data = image.convert("RGB").tobytes()
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, image.width, image.height, 0, GL_RGB, GL_UNSIGNED_BYTE, image_data)
// setting the view, projection etc matrix
Since I am working with PyQt5, I have this class were I load the object:
class Obj(QOpenGLWidget):
def __init__(self, parent):
QOpenGLWidget.__init__(self, parent)
def initializeGL(self):
glClearColor(82/255, 95/255, 107/255, 1.0)
glEnable(GL_DEPTH_TEST)
self.model = Model("....obj", "texture.png")
def paintGL(self):
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)
glDrawArrays(GL_TRIANGLES, 0, len(self.model.object.vertex_index))
self.update()
def resizeGL(self, width, height):
glViewport(0, 0, width, height)
What I am struggling with is creating in the Obj class another Model. I tried creating it in initializeGL and rendering in paintGL with another glDrawArrays call, but nothing happened, or nothing expected... Any idea of what I am doing wrong?
You have to use a Vertex Array Object. A Vertex Array Object stores all of the state needed to supply vertex data.
The constructor of the model only creates all the required OpenGL objects. The shader program is not part of the model. Use the same program for all models:
class Model:
def __init__(self, file_name, texture_name):
self.object = ObjectLoader()
self.object.load_model(file_name)
# create vertex array object
self.vao = glGenVertexArrays(1)
glBindVertexArray(self.vao)
vertex_buffer_object = GLuint(0)
glGenBuffers(1, vertex_buffer_object)
glBindBuffer(GL_ARRAY_BUFFER, vertex_buffer_object)
glBufferData(GL_ARRAY_BUFFER, len(self.object.model) * 4, self.object.c_model, GL_DYNAMIC_DRAW)
glDrawArrays(GL_TRIANGLES, 0, len(self.object.vertex_index))
# vertices
vertex_offset = 0
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, self.object.model.itemsize * 3, ctypes.c_void_p(vertex_offset))
glEnableVertexAttribArray(0)
# textures
texture_offset = len(self.object.vertex_index) * 12
glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, self.object.model.itemsize * 2, ctypes.c_void_p(texture_offset))
glEnableVertexAttribArray(1)
# normals
normal_offset = texture_offset + len(self.object.texture_index) * 8
glVertexAttribPointer(2, 3, GL_FLOAT, GL_FALSE, self.object.model.itemsize * 3, ctypes.c_void_p(normal_offset))
glEnableVertexAttribArray(2)
# create texture
self.texture = GLuint(0)
glGenTextures(1, self.texture)
glBindTexture(GL_TEXTURE_2D, self.texture)
# texture wrapping
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT)
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT)
# texture filtering
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST)
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST)
image = Image.open(texture_name)
flipped_image = image.transpose(Image.FLIP_TOP_BOTTOM)
image_data = image.convert("RGB").tobytes()
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, image.width, image.height, 0, GL_RGB, GL_UNSIGNED_BYTE, image_data)
Add a method to the class which binds the OpenGL objects (VAO and texture) and draws the mesh:
class Model:
# [...]
def draw(self):
// bind texture
glBindTexture(GL_TEXTURE_2D, self.texture)
// create vertex array object
glBindVertexArray(self.vao)
// draw object
glDrawArrays(GL_TRIANGLES, 0, len(self.object.vertex_index))
With this setup you can draw multiple models:
class Obj(QOpenGLWidget):
def __init__(self, parent):
QOpenGLWidget.__init__(self, parent)
def initializeGL(self):
glClearColor(82/255, 95/255, 107/255, 1.0)
glEnable(GL_DEPTH_TEST)
glEnable(GL_BLEND)
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)
self.model1 = Model("....obj", "texture.png")
self.model2 = Model("....obj", "texture2.png")
# [...]
def paintGL(self):
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)
# install program
glUseProgram(self.shader)
# setting the view, projection etc matrix
# [...]
self.model1.draw()
self.model2.draw()
# [...]
self.update()
def resizeGL(self, width, height):
glViewport(0, 0, width, height)
The models can be organized in a list, too:
class Obj(QOpenGLWidget):
# [...]
def initializeGL(self):
# [...]
self.models = [
Model("....obj", "texture.png"),
Model("....obj", "texture2.png")
# [...]
]
def paintGL(self):
# [...]
for model in self.models:
model.draw()
# [...]
So I've been racking my brain with nonstop trial and error. I feel like I keep coming back with questions about the same thing and it is frustrating.
I am following the tutorials and looking at the C++ code and following along with Python trying to replicate the result. I've noticed there are subtle differences through trial and error and I've searched high and low on the internet to see if someone else has experienced my issue. 9/10 times it is because the texture was not bound, however, it my case it is.
https://learnopengl.com/code_viewer_gh.php?code=src/1.getting_started/4.1.textures/textures.cpp
So I am wondering what I am doing wrong... any help?
import os # For mac... I am using a mac
import glfw # We're using this instead of GLUT as we have more flexibility
import numpy as np # We will use numpy for our arrays
# Using the API wrapper instead of something mugh higher. Keep in mind that the API is a state machine
from OpenGL.GL import *
from OpenGL.arrays import *
from ctypes import c_void_p
from PIL import Image
class HelloWindow():
width = 800
height = 640
title = 'Hello Window'
window = None
shader_program = None
vao = None
vbo = None
texture = None
vertex_gsl = """
#version 410 core
layout (location = 0) in vec3 aPos;
layout (location = 1) in vec3 aColor;
layout (location = 3) in vec2 aTexCoord;
out vec3 ourColor;
out vec2 TexCoord;
void main() {
gl_Position = vec4(aPos, 1.0);
ourColor = aColor;
TexCoord = vec2(aTexCoord);
}
"""
fragment_gsl = """
#version 410 core
out vec4 FragColor;
in vec3 ourColor;
in vec2 TexCoord;
uniform sampler2D texture1;
void main() {
FragColor = texture(texture1, TexCoord);
}
"""
def __init__(self):
if not glfw.init():
raise TypeError('Unable to initalize glfw')
self.main()
def main(self):
# Set window hints
self.set_window_hints()
# Create the window
self.create_window()
max_vertex_attributes = glGetIntegerv(GL_MAX_VERTEX_ATTRIBS)
print('Maximum number of vertex attributes in a vertex shader is: ' + str(max_vertex_attributes))
# Keep the window open in a loop
self.loop()
def set_window_hints(self):
glfw.window_hint(glfw.SAMPLES, 4)
# Using the core version in Mac OS but can be set to something else
glfw.window_hint(glfw.CONTEXT_VERSION_MAJOR, 4)
glfw.window_hint(glfw.CONTEXT_VERSION_MINOR, 1)
glfw.window_hint(glfw.OPENGL_PROFILE, glfw.OPENGL_CORE_PROFILE)
glfw.window_hint(glfw.OPENGL_FORWARD_COMPAT, True)
def create_window(self):
self.window = glfw.create_window(self.width, self.height, self.title, None, None)
if not self.window:
raise TypeError('Unable to create the window')
glfw.terminate()
glfw.make_context_current(self.window)
glfw.set_framebuffer_size_callback(self.window, self.frame_buffer_size)
### Let's setup our data
verts = np.array([
# positions # colors # texture coords
[-0.5, 0.5, 0.0, 1.0, 0.0, 0.0, 0.0, 1.0],
[-0.5, -0.5, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0],
[0.5, -0.5, 0.0, 0.0, 0.0, 1.0, 1.0, 0.0],
[0.5, 0.5, 0.0, 1.0, 1.0, 0.0, 1.0, 1.0]
], dtype=np.float32) # OpenGL expects 32 bit data. Not 64 bit if you're on a 64 bit machine
indicies = np.array([
[0, 1, 3],
[1, 2, 3]
], dtype=np.uint32)
self.vao = glGenVertexArrays(1)
self.vbo = glGenBuffers(1)
self.ebo = glGenBuffers(1)
glBindVertexArray(self.vao)
glBindBuffer(GL_ARRAY_BUFFER, self.vbo) # Bind the buffer as an array buffer and not an element buffer
glBufferData(GL_ARRAY_BUFFER, verts, GL_STATIC_DRAW)
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, self.ebo)
glBufferData(GL_ELEMENT_ARRAY_BUFFER, indicies, GL_STATIC_DRAW)
glVertexAttribPointer(0, 3, GL_FLOAT, False, 8 * verts.itemsize, None)
glEnableVertexAttribArray(0)
glVertexAttribPointer(1, 3, GL_FLOAT, False, 8 * verts.itemsize, c_void_p(3 * verts.itemsize))
glEnableVertexAttribArray(1)
glVertexAttribPointer(2, 2, GL_FLOAT, False, 8 * verts.itemsize, c_void_p(6 * verts.itemsize))
glEnableVertexAttribArray(2)
glBindBuffer(GL_ARRAY_BUFFER, 0)
glBindVertexArray(0)
self.setup_shader()
self.texture = glGenTextures(1)
glBindTexture(GL_TEXTURE_2D, self.texture)
glPixelStorei(GL_UNPACK_ALIGNMENT, 1)
# Set the wrapping texture parameters for x,y equivalents
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT)
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT)
# Set the texture filtering parameters from min to max
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR)
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR)
# Load our image
img = Image.open('../res/container.jpg')
img.transpose(Image.FLIP_TOP_BOTTOM)
img.convert('RGB')
data = np.array(list(img.getdata()), np.uint8)
# Set the texture data
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, img.width, img.height, 0, GL_RGB, GL_UNSIGNED_BYTE, data)
glGenerateMipmap(GL_TEXTURE_2D)
#img.close()
def loop(self):
while not glfw.window_should_close(self.window):
glClearColor(0.2, 0.3, 0.3, 1.0)
glClear(GL_COLOR_BUFFER_BIT) # We always use this after we clear the color
# Maintain aspect ratio (optional)
glfw.set_window_aspect_ratio(self.window, self.width, self.height)
# Handle input
self.capture_input()
# Bind the texture
glBindTexture(GL_TEXTURE_2D, self.texture)
# Do some rendering
glUseProgram(self.shader_program)
glBindVertexArray(self.vao)
#glEnableVertexAttribArray(0)
#glDrawArrays(GL_TRIANGLES, 0, 3)
glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, None)
glfw.swap_interval(1)
glfw.swap_buffers(self.window)
glfw.poll_events()
glfw.destroy_window(self.window)
# Add the vao and vbo to an array so they're deleted. We can use this when creating several vaos and vbos
vao_list = np.array([self.vao], dtype=np.uint32)
vbo_list = np.array([self.vbo], dtype=np.uint32)
glDeleteVertexArrays(1, vao_list)
glDeleteBuffers(1, vbo_list)
glfw.terminate()
def capture_input(self):
if glfw.get_key(self.window, glfw.KEY_ESCAPE) == glfw.PRESS: # Get the key pressed and check if it is escape key
glfw.set_window_should_close(self.window, True)
def frame_buffer_size(self, window, width, height):
glViewport(0, 0, width, height)
def setup_shader(self):
vertex_shader = glCreateShader(GL_VERTEX_SHADER)
glShaderSource(vertex_shader, self.vertex_gsl)
glCompileShader(vertex_shader)
if not glGetShaderiv(vertex_shader, GL_COMPILE_STATUS):
glGetShaderInfoLog(vertex_shader, 512, None)
raise TypeError('vertex_shader did not compile correctly. Check the GSL')
fragment_shader = glCreateShader(GL_FRAGMENT_SHADER)
glShaderSource(fragment_shader, self.fragment_gsl)
glCompileShader(fragment_shader)
if not glGetShaderiv(fragment_shader, GL_COMPILE_STATUS):
glGetShaderInfoLog(fragment_shader, 512, None)
raise TypeError('fragment_shader did not compile correctly. Check the GSL')
self.shader_program = glCreateProgram()
glAttachShader(self.shader_program, vertex_shader)
glAttachShader(self.shader_program, fragment_shader)
glLinkProgram(self.shader_program)
if not glGetProgramiv(self.shader_program, GL_LINK_STATUS):
glGetProgramInfoLog(self.shader_program, 512, None)
glDeleteShader(vertex_shader)
glDeleteShader(fragment_shader)
if __name__ == '__main__':
hello_window = HelloWindow()
In the program the attribute index specified for the array of texture coordinates is 2:
glVertexAttribPointer(2, 2, GL_FLOAT, False, 8 * verts.itemsize, c_void_p(6 * verts.itemsize))
glEnableVertexAttribArray(2)
But in the vertex shader the attribute index specified for the texture coordinates is 3, by Layout Qualifier:
layout (location = 3) in vec2 aTexCoord;
Use the same attribute index in both cases and your texture will show up.
import textwrap
import numpy as np
from ctypes import *
from OpenGL.GL import *
from OpenGL.GL.ARB.multitexture import *
from OpenGL.GLU import *
from OpenGL.GLUT import *
class TestOpenglManager():
# -------- Magic functions --------
def __init__(self):
self.window_width = 800
self.window_height = 800
# -------- Glut stuff --------
def reshape(self, w, h):
self.window_width = w
self.window_height = h
def animate(self):
glutPostRedisplay()
def visible(self, vis):
if (vis == GLUT_VISIBLE):
glutIdleFunc(self.animate)
else:
glutIdleFunc(0)
def key_pressed(self, *args):
if args[0] == b"\x1b":
sys.exit()
def run(self):
glutInit(sys.argv)
glutInitDisplayMode(GLUT_RGBA | GLUT_DOUBLE | GLUT_ALPHA | GLUT_DEPTH)
glutInitWindowSize(self.window_width, self.window_height)
glutInitWindowPosition(800, 100)
glutCreateWindow(b'Test')
glutDisplayFunc(self.display)
glutReshapeFunc(self.reshape)
glutIdleFunc(self.animate)
glutVisibilityFunc(self.visible)
glutKeyboardFunc(self.key_pressed)
self.init()
glutMainLoop()
# -------- Resource allocation --------
def init_shaders(self):
def make_vs(source):
vs = glCreateShader(GL_VERTEX_SHADER)
glShaderSource(vs, source)
glCompileShader(vs)
result = glGetShaderiv(vs, GL_COMPILE_STATUS)
if not(result):
raise Exception("Error: {0}".format(
glGetShaderInfoLog(vs)
))
return vs
def make_fs(source):
fs = glCreateShader(GL_FRAGMENT_SHADER)
glShaderSource(fs, source)
glCompileShader(fs)
result = glGetShaderiv(fs, GL_COMPILE_STATUS)
if not(result):
raise Exception("Error: {0}".format(
glGetShaderInfoLog(fs)
))
return fs
def make_program(vs, fs):
program = glCreateProgram()
glAttachShader(program, vs)
glAttachShader(program, fs)
glLinkProgram(program)
return program
vs = textwrap.dedent("""
#version 130
in vec3 position;
void main()
{
gl_Position = vec4(position, 1.0f);
}
""")
fs = textwrap.dedent("""
#version 130
out vec4 frag_color;
uniform vec3 color;
void main() {
frag_color = vec4(color,1.0);
}
""")
self.prog = make_program(make_vs(vs), make_fs(fs))
def init_vbos(self):
vertices = np.array([
# 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,
], dtype=np.float32)
indices = np.array([
0, 1, 3,
1, 2, 3
], dtype=np.int32)
vao_id = glGenVertexArrays(1)
vbo_id = glGenBuffers(1)
ebo_id = glGenBuffers(1)
glBindVertexArray(vao_id)
print("Vertices: Uploading {0} bytes".format(
ArrayDatatype.arrayByteCount(vertices)))
glBindBuffer(GL_ARRAY_BUFFER, vbo_id)
glBufferData(
GL_ARRAY_BUFFER,
ArrayDatatype.arrayByteCount(vertices),
vertices, GL_STATIC_DRAW
)
print("Indices: Uploading {0} bytes".format(
ArrayDatatype.arrayByteCount(indices)))
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ebo_id)
glBufferData(
GL_ELEMENT_ARRAY_BUFFER,
ArrayDatatype.arrayByteCount(indices),
indices, GL_STATIC_DRAW
)
print("Position: Location {0}".format(
glGetAttribLocation(self.prog, "position")))
vertex_stride = 3
glEnableVertexAttribArray(glGetAttribLocation(self.prog, "position"))
glVertexAttribPointer(glGetAttribLocation(self.prog, "position"),
3, GL_FLOAT, GL_FALSE, vertex_stride, c_void_p(0)
)
glBindBuffer(GL_ARRAY_BUFFER, 0)
glBindVertexArray(0)
self.obj_metadata = {
"vao_id": vao_id,
"vbo_id": vbo_id,
"ebo_id": ebo_id,
"type": "3v3c3t",
"stride": vertex_stride,
"indices": len(indices),
"vertices": vertices,
"num_triangles": int(len(vertices) / vertex_stride),
"num_vertices": len(vertices)
}
def init(self):
glClearColor(0.0, 0.0, 0.0, 0.0)
# glEnable(GL_MULTISAMPLE)
# glEnable(GL_DEPTH_TEST)
# glEnable(GL_TEXTURE_2D)
self.init_shaders()
self.init_vbos()
def display(self):
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)
glBindVertexArray(self.obj_metadata["vao_id"])
glUseProgram(self.prog)
glUniform3f(glGetUniformLocation(self.prog, "color"), 0.0, 1.0, 0.0)
glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0)
glBindVertexArray(0)
glUseProgram(0)
glBindVertexArray(0)
glutSwapBuffers()
if __name__ == "__main__":
TestOpenglManager().run()
I've been trying to figure out for a while what could be wrong with the above snippet and I don't understand what's wrong, it should drawing a green triangle but instead I'll get a black screen
What's going on?
First of all your vertex_stride is incorrect. It should be 0 not 3. Lastly with PyOpenGL when calling glDrawElements you need to pass None or ctypes.c_void_p(0) and not 0.
The thing is that the stride is a byte offset between each vertex attribute. So as your vertex is laid out as:
X Y Z
X Y Z
X Y Z
Then there is an offset of 0 bytes between the next pair of X, Y, Z. However if your vertex was laid out in this manner:
X Y Z R G B
X Y Z R G B
X Y Z R G B
Then having the stride as 3 (or more correctly 3 * sizeof(float)) would be correct.