Related
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)
I'm fairly new to OpenGL and I tried recreating the tutorial from https://learnopengl.com/Getting-started/Hello-Triangle to draw a rectangle in PyOpenGL.
(Original source code: https://learnopengl.com/code_viewer_gh.php?code=src/1.getting_started/2.2.hello_triangle_indexed/hello_triangle_indexed.cpp)
The first part of the tutorial that only draws a triangle using glDrawArrays works perfectly but when I try to use glDrawElements nothing is drawn. It doesn't even raise an error, it just shows me a black screen. I'm pretty sure I copied the instructions from the tutorial one by one and since there is no error message, I have no idea what I did wrong.
I would appreciate any sort of help.
My code:
from OpenGL.GL import *
import OpenGL.GL.shaders
import numpy as np
class Shaders:
def vertex(self):
v = """
#version 330 core
layout (location = 0) in vec3 aPos;
void main()
{
gl_Position = vec4(aPos.x, aPos.y, aPos.z, 1.0);
}
"""
return OpenGL.GL.shaders.compileShader(v, GL_VERTEX_SHADER)
def fragment(self):
f = """
#version 330 core
out vec4 FragColor;
void main()
{
FragColor = vec4(1.0f, 0.5f, 0.2f, 1.0f);
}
"""
return OpenGL.GL.shaders.compileShader(f, GL_FRAGMENT_SHADER)
def main():
# glfw: initialize and configure
if not glfw.init():
return
glfw.window_hint(glfw.CONTEXT_VERSION_MAJOR, 3)
glfw.window_hint(glfw.CONTEXT_VERSION_MINOR, 2)
glfw.window_hint(glfw.OPENGL_PROFILE, glfw.OPENGL_CORE_PROFILE)
glfw.window_hint(glfw.OPENGL_FORWARD_COMPAT, GL_TRUE)
window = glfw.create_window(1920, 1080, "Hello World", None, None)
if not window:
glfw.terminate()
return
glfw.make_context_current(window)
# build and compile shaders
s = Shaders()
shader = OpenGL.GL.shaders.compileProgram(s.vertex(), s.fragment())
# set up vertex data and buffers and configure vertex attributes
vertices = np.array([
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
])
VAO = glGenVertexArrays(1)
VBO = glGenBuffers(1)
EBO = glGenBuffers(1)
glBindVertexArray(VAO)
glBindBuffer(GL_ARRAY_BUFFER, VBO)
glBufferData(GL_ARRAY_BUFFER, 48, vertices, GL_STATIC_DRAW)
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO)
glBufferData(GL_ELEMENT_ARRAY_BUFFER, 12, indices, GL_STATIC_DRAW)
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 0, 0)
glEnableVertexAttribArray(0)
glBindBuffer(GL_ARRAY_BUFFER, 0)
glBindVertexArray(0)
# render loop
while not glfw.window_should_close(window):
glClearColor(0, 0, 0, 1.0)
glClear(GL_COLOR_BUFFER_BIT)
glUseProgram(shader)
glBindVertexArray(VAO)
glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, None)
glfw.swap_buffers(window)
glfw.poll_events()
glfw.terminate()
if __name__ == "__main__":
main()
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):
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 0, 0)
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 0, None)
The index buffer consist of 6 indices of the type uint32. Hence the size of the index buffer is 24 instead of 12:
glBufferData(GL_ELEMENT_ARRAY_BUFFER, 12, indices, GL_STATIC_DRAW)
glBufferData(GL_ELEMENT_ARRAY_BUFFER, 24, indices, GL_STATIC_DRAW)
When using PyOpenGL the size parameter can be ommited (see glBufferData). In this case the size off the array is used:
glBufferData(GL_ELEMENT_ARRAY_BUFFER, indices, GL_STATIC_DRAW)
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()
# [...]
I recently got into OpenGL, and used PyOpenGL and the fixed-function pipeline (I know, I know) to draw cubes and stuff.
Anyway, everyone told me that fixed-function is horrible and deprecated, so I got into core-profile OpenGL just now. I've been followig this tutorial, which is in C++, by just transforming everything into Python with basically the same libraries.
I've gotten to the point where I want to render a single 2D triangle using VBOs and VAOs, and the code runs, but doesn't actually draw anything.
Here it is (bear in mind, I'm totally new to this, so I probably messed up an elementary function call somewhere, and I don't really know how everything works):
import numpy as np
import glfw
from OpenGL.GL import *
def main():
glfw.init()
glfw.window_hint(glfw.CONTEXT_VERSION_MAJOR, 4)
glfw.window_hint(glfw.CONTEXT_VERSION_MINOR, 5)
glfw.window_hint(glfw.OPENGL_PROFILE, glfw.OPENGL_CORE_PROFILE)
window = glfw.create_window(800, 600, "helo wold", None, None)
glfw.make_context_current(window)
vertices = np.array([-0.5, -0.5, 0, 0.5, -0.5, 0, 0, 0.5, 0], dtype = 'float32')
vertexShaderSource = '''#version 450 core
layout (location = 0) in vec3 aPos;
void main() {
gl_Position = vec4(aPos.x, aPos.y, aPos.z, 1.0);
}
'''
vertexShader = glCreateShader(GL_VERTEX_SHADER)
glShaderSource(vertexShader, vertexShaderSource)
glCompileShader(vertexShader)
fragmentShaderSource = '''#version 450 core
out vec4 FragColor;
void main() {
FragColor = vec4(1.0f, 0.5f, 0.2f, 1.0f);
}
'''
fragmentShader = glCreateShader(GL_FRAGMENT_SHADER)
glShaderSource(fragmentShader, fragmentShaderSource)
glCompileShader(fragmentShader)
shaderProgram = glCreateProgram(1)
glAttachShader(shaderProgram, vertexShader)
glAttachShader(shaderProgram, fragmentShader)
glLinkProgram(shaderProgram)
glDeleteShader(vertexShader)
glDeleteShader(fragmentShader)
vbo, vao = glGenBuffers(1), glGenVertexArrays(1)
glBindVertexArray(vao)
glBindBuffer(GL_ARRAY_BUFFER, vbo)
glBufferData(GL_ARRAY_BUFFER, len(vertices), vertices, GL_STATIC_DRAW)
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 12, 0)
glEnableVertexAttribArray(0)
glBindBuffer(GL_ARRAY_BUFFER, 0)
glBindVertexArray(0)
while not glfw.window_should_close(window):
glClearColor(0.2, 0.3, 0.3, 1.0)
glClear(GL_COLOR_BUFFER_BIT)
glUseProgram(shaderProgram)
glBindVertexArray(vao)
glDrawArrays(GL_TRIANGLES, 0, 3)
glfw.swap_buffers(window)
glfw.poll_events()
glfw.terminate()
if __name__ == '__main__':
main()
It opens the window fine, and has the background color I gave it, but it doesn't draw the triangle in any way.
I'd love it if someone could tell me what I'm doing wrong, thanks!
The 2nd parameter of glBufferData has to be the size in bytes, len(vertices)*4 rather than len(vertices):
glBufferData(GL_ARRAY_BUFFER, len(vertices)*4, vertices, GL_STATIC_DRAW)
Since PyOpenGL's glBufferData is overloaded, the size parameter can be omitted (if data is a ctypes array or numpy.array):
glBufferData(GL_ARRAY_BUFFER, vertices, GL_STATIC_DRAW)
The last parameter of glVertexAttribPointer ahs to be of type const GLvoid *. Thus it has to be None or ctypes.c_void_p(0) rather than 0:
Either
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 12, None)
or
import ctypes
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 12, ctypes.c_void_p(0))
Further more I recommend to evaluate if the shaders are compiled successfully (glGetShaderiv):
vertexShader = glCreateShader(GL_VERTEX_SHADER)
glShaderSource(vertexShader, vertexShaderSource)
glCompileShader(vertexShader)
if not glGetShaderiv(vertexShader, GL_COMPILE_STATUS ):
print('vertex shader compile error:')
print(glGetShaderInfoLog(vertexShader))
fragmentShader = glCreateShader(GL_FRAGMENT_SHADER)
glShaderSource(fragmentShader, fragmentShaderSource)
glCompileShader(fragmentShader)
if not glGetShaderiv(fragmentShader, GL_COMPILE_STATUS):
print('fragment shader compile error:')
print(glGetShaderInfoLog(fragmentShader))
And the program is linked successfully (glGetProgramiv):
glLinkProgram(shaderProgram)
if not glGetProgramiv(shaderProgram, GL_LINK_STATUS):
print('link error:')
print(glGetProgramInfoLog(shaderProgram))