Here is my code so far:
main.py_
from Application import Application
if __name__ == '__main__':
app = Application()
app.run()
Triangle.py_
import contextlib
import logging as log
from OpenGL import GL as gl
import ctypes
import sys
class Triangle:
vertex_array_id = 0
vertex_data = []
program_id = 0
shader_id = 0
def __init__(self):
print('Triangle.__init__(self)')
self.create_vertex_buffer()
self.load_shaders()
def create_vertex_array_object(self):
log.debug('create_vertex_array_object(self):')
self.vertex_array_id = gl.glGenVertexArrays(1)
try:
gl.glBindVertexArray(self.vertex_array_id)
yield
finally:
log.debug('~create_vertex_array_object(self):')
gl.glDeleteVertexArrays(1, [self.vertex_array_id])
def create_vertex_buffer(self):
with self.create_vertex_array_object():
# A triangle
self.vertex_data = [-1, -1, 0,
1, -1, 0,
0, 1, 0]
attr_id = 0 # No particular reason for 0,
# but must match the layout location in the shader.
log.debug('creating and binding the vertex buffer (VBO)')
vertex_buffer = gl.glGenBuffers(1)
try:
gl.glBindBuffer(gl.GL_ARRAY_BUFFER, vertex_buffer)
array_type = (gl.GLfloat * len(self.vertex_data))
gl.glBufferData(gl.GL_ARRAY_BUFFER,
len(self.vertex_data) * ctypes.sizeof(ctypes.c_float),
array_type(*self.vertex_data),
gl.GL_STATIC_DRAW)
log.debug('setting the vertex attributes')
gl.glVertexAttribPointer(
attr_id, # attribute 0.
3, # components per vertex attribute
gl.GL_FLOAT, # type
False, # to be normalized?
0, # stride
None # array buffer offset
)
gl.glEnableVertexAttribArray(attr_id) # use currently bound VAO
yield
finally:
log.debug('cleaning up buffer')
# gl.glDisableVertexAttribArray(attr_id)
# gl.glDeleteBuffers(1, [vertex_buffer])
def load_shaders(self):
shaders = {
gl.GL_VERTEX_SHADER: '''\
#version 330 core
layout(location = 0) in vec3 vertexPosition_modelspace;
void main(){
gl_Position.xyz = vertexPosition_modelspace;
gl_Position.w = 1.0;
}
''',
gl.GL_FRAGMENT_SHADER: '''\
#version 330 core
out vec3 color;
void main(){
color = vec3(1,0,0);
}
'''
}
log.debug('creating the shader program')
self.program_id = gl.glCreateProgram()
try:
shader_ids = []
for shader_type, shader_src in shaders.items():
self.shader_id = gl.glCreateShader(shader_type)
gl.glShaderSource(self.shader_id, shader_src)
log.debug(f'compiling the {shader_type} shader')
gl.glCompileShader(self.shader_id)
# check if compilation was successful
result = gl.glGetShaderiv(self.shader_id, gl.GL_COMPILE_STATUS)
info_log_len = gl.glGetShaderiv(self.shader_id, gl.GL_INFO_LOG_LENGTH)
if info_log_len:
logmsg = gl.glGetShaderInfoLog(self.shader_id)
log.error(logmsg)
sys.exit(10)
gl.glAttachShader(self.program_id, self.shader_id)
shader_ids.append(self.shader_id)
log.debug('linking shader program')
gl.glLinkProgram(self.program_id)
# check if linking was successful
result = gl.glGetProgramiv(self.program_id, gl.GL_LINK_STATUS)
info_log_len = gl.glGetProgramiv(self.program_id, gl.GL_INFO_LOG_LENGTH)
if info_log_len:
logmsg = gl.glGetProgramInfoLog(self.program_id)
log.error(logmsg)
sys.exit(11)
log.debug('installing shader program into rendering state')
gl.glUseProgram(self.program_id)
yield
finally:
log.debug('cleaning up shader program')
for self.shader_id in self.shader_ids:
gl.glDetachShader(self.program_id, self.shader_id)
gl.glDeleteShader(self.shader_id)
gl.glUseProgram(0)
gl.glDeleteProgram(self.program_id)
def render(self):
gl.glDrawArrays(gl.GL_TRIANGLES, 0, 3) # Starting from vertex 0
Application.py_
import contextlib
import glfw
import sys
from OpenGL import GL as gl
import logging as log
from Triangle import Triangle
class Application:
tri = Triangle()
def __init__(self):
print("Application.__init__(self)")
#contextlib.contextmanager
def create_main_window(self):
log.debug('create_main_window(self):')
if not glfw.init():
sys.exit(1)
try:
glfw.window_hint(glfw.CONTEXT_VERSION_MAJOR, 3)
glfw.window_hint(glfw.CONTEXT_VERSION_MINOR, 3)
glfw.window_hint(glfw.OPENGL_FORWARD_COMPAT, True)
glfw.window_hint(glfw.OPENGL_PROFILE, glfw.OPENGL_CORE_PROFILE)
title = 'Tutorial 2: First Triangle'
window = glfw.create_window(500, 400, title, None, None)
if not window:
sys.exit(2)
glfw.make_context_current(window)
gl.glClearColor(0, 0, 0.4, 0)
yield window
finally:
log.debug('~create_main_window(self):')
glfw.terminate()
def main_loop(self, window):
while (
glfw.get_key(window, glfw.KEY_ESCAPE) != glfw.PRESS and
not glfw.window_should_close(window)
):
gl.glClear(gl.GL_COLOR_BUFFER_BIT | gl.GL_DEPTH_BUFFER_BIT)
self.tri.render()
glfw.swap_buffers(window)
glfw.poll_events()
def run(self):
log.basicConfig(level=log.DEBUG)
with self.create_main_window() as window:
self.main_loop(window)
From the console I have this coming out:
DEBUG:root:create_main_window(self):
Triangle.__init__(self)
Application.__init__(self)
DEBUG:root:~create_main_window(self):
Traceback (most recent call last):
File "C:\Users\prussos\PycharmProjects\pythonProject3\main.py", line 5, in <module>
app.run()
File "C:\Users\prussos\PycharmProjects\pythonProject3\Application.py", line 51, in run
self.main_loop(window)
File "C:\Users\prussos\PycharmProjects\pythonProject3\Application.py", line 44, in main_loop
self.tri.render()
File "C:\Users\prussos\PycharmProjects\pythonProject3\Triangle.py", line 131, in render
gl.glDrawArrays(gl.GL_TRIANGLES, 0, 3) # Starting from vertex 0
File "C:\Users\prussos\PycharmProjects\pythonProject3\venv\lib\site-packages\OpenGL\platform\baseplatform.py", line 415, in __call__
return self( *args, **named )
File "C:\Users\prussos\PycharmProjects\pythonProject3\venv\lib\site-packages\OpenGL\error.py", line 230, in glCheckError
raise self._errorClass(
OpenGL.error.GLError: GLError(
err = 1282,
description = b'invalid operation',
baseOperation = glDrawArrays,
cArguments = (GL_TRIANGLES, 0, 3)
)
Process finished with exit code 1
There seems to be a drawing error that is happening but I really I not quite exactly sure how to start to troubleshoot this thing yet. The code was moved over from gitlabs into a class based system for python. So a draw arrays that is an invalid operation what could that be coming from in OpenGL library is anyone familiar with it enough to know what place to edit here?
When you call an OpenGL instruction, you need a valid and up-to-date OpenGL context. The OpenGL Context is created with the OpenGl window. The OpenGL objects (VAO, VBO and shaders) are create in the constructor of the Triangle class. Therefore you can construct an object of this class only after creating the OpenGL window:
class Application:
# tri = Triangle() # <-- DELETE
def __init__(self):
print("Application.__init__(self)")
#contextlib.contextmanager
def create_main_window(self):
log.debug('create_main_window(self):')
if not glfw.init():
sys.exit(1)
try:
glfw.window_hint(glfw.CONTEXT_VERSION_MAJOR, 3)
glfw.window_hint(glfw.CONTEXT_VERSION_MINOR, 3)
glfw.window_hint(glfw.OPENGL_FORWARD_COMPAT, True)
glfw.window_hint(glfw.OPENGL_PROFILE, glfw.OPENGL_CORE_PROFILE)
title = 'Tutorial 2: First Triangle'
window = glfw.create_window(500, 400, title, None, None)
if not window:
sys.exit(2)
glfw.make_context_current(window)
gl.glClearColor(0, 0, 0.4, 0)
yield window
finally:
log.debug('~create_main_window(self):')
glfw.terminate()
def main_loop(self, window):
self.tri = Triangle() # IINSERT
while (
glfw.get_key(window, glfw.KEY_ESCAPE) != glfw.PRESS and
not glfw.window_should_close(window)
):
gl.glClear(gl.GL_COLOR_BUFFER_BIT | gl.GL_DEPTH_BUFFER_BIT)
self.tri.render()
glfw.swap_buffers(window)
glfw.poll_events()
def run(self):
log.basicConfig(level=log.DEBUG)
with self.create_main_window() as window:
self.main_loop(window)
Related
I try to load in the texture using this method:
#classmethod
def load_texture(cls, file_name: str):
try:
img = Image.open(f"{sys.path[0]}/res/{file_name}.png")
except Exception as e:
print(e)
raise SystemExit
try:
ix, iy, image = img.size[0], img.size[1], img.tobytes("raw", "RGBA", 0, -1)
except SystemError:
ix, iy, image = img.size[0], img.size[1], img.tobytes("raw", "RGBX", 0, -1)
texture_id = glGenTextures(1) # generate a texture texture_id
cls.__textures.append(texture_id)
glBindTexture(GL_TEXTURE_2D, texture_id) # make it current
glPixelStorei(GL_UNPACK_ALIGNMENT, 1)
# copy the texture into the current texture texture_id
glTexImage2D(GL_TEXTURE_2D, 0, 3, ix, iy, 0, GL_RGBA, GL_UNSIGNED_BYTE, image)
return texture_id
And display it with this method:
#staticmethod
def render(model: TexturedModel):
raw_model = model.get_raw_model()
glBindVertexArray(raw_model.get_vao_id()) # bind the desired VAO to be able to use it
glEnableVertexAttribArray(0) # we have put the indices in the 0th address
glEnableVertexAttribArray(1) # we have put the textures in the 1st address
glActiveTexture(GL_TEXTURE0)
glBindTexture(GL_TEXTURE_2D, model.get_texture().get_id())
glDrawElements(GL_TRIANGLES, raw_model.get_vertex_count(), GL_UNSIGNED_INT, None)
glDisableVertexAttribArray(0) # disable the attributeList after using it
glDisableVertexAttribArray(1) # disable the attributeList after using it
glBindVertexArray(0) # unbind the VAO
What exactly is going wrong? I suppose the texture isn't getting loaded in right as it's displayed black.
Edit: I think it could also be something wrong with the shaders so here is my:
shader_program.py:
from OpenGL.GL import *
from OpenGL.GLUT import *
from abc import abstractmethod # for abstract methods
class ShaderProgram:
"""
Class for loading and linking all shaders to the program.
"""
def __init__(self, vertex_file: str, fragment_file: str):
self.__vertex_shader_id = self.load_shader(vertex_file, GL_VERTEX_SHADER)
self.__fragment_shader_id = self.load_shader(fragment_file, GL_FRAGMENT_SHADER)
self.__program_id = glCreateProgram() # create program
glAttachShader(self.__program_id, self.__vertex_shader_id) # attach the shader to the program
glAttachShader(self.__program_id, self.__fragment_shader_id) # attach the shader to the program
self.bind_attributes()
glLinkProgram(self.__program_id) # link the program to the shaders
glValidateProgram(self.__program_id) # validate the program
def start(self):
glUseProgram(self.get_program_id())
#staticmethod
def stop():
glUseProgram(0)
def clean_up(self):
self.stop()
glDetachShader(self.get_program_id(), self.get_vertex_shader_id())
glDetachShader(self.get_program_id(), self.get_fragment_shader_id())
glDeleteShader(self.get_vertex_shader_id())
glDeleteShader(self.get_fragment_shader_id())
glDeleteProgram(self.get_program_id())
#abstractmethod
def bind_attributes(self):
pass
def bind_attribute(self, attribute: int, variable_name: str):
glBindAttribLocation(self.get_program_id(), attribute, variable_name)
#staticmethod
def load_shader(file: str, shader_type: int):
try:
shader_file = open(file, 'r') # read file
shader_source = ''.join(shader_file.readlines()) # create a continuous string
except Exception as e:
print(e) # print exception
raise SystemExit
shader_id = glCreateShader(shader_type) # create the shader
glShaderSource(shader_id, shader_source) # load the shader source code
glCompileShader(shader_id) # compile the shader
if glGetShaderiv(shader_id, GL_COMPILE_STATUS) == GL_FALSE:
print(glGetShaderInfoLog(shader_id)) # print the info log if it didn't compile correctly
print("Could not compile shader.")
raise SystemExit
return shader_id
def get_program_id(self):
return self.__program_id
def get_vertex_shader_id(self):
return self.__vertex_shader_id
def get_fragment_shader_id(self):
return self.__fragment_shader_id
my static_shader.py:
from .shader_program import ShaderProgram
import sys
class StaticShader(ShaderProgram):
__VERTEX_FILE = f"{sys.path[0]}/shaders/vertexShader.txt"
__FRAGMENT_FILE = f"{sys.path[0]}/shaders/fragmentShader.txt"
def __init__(self):
super().__init__(self.get_vertex_file(), self.get_fragment_file())
def bind_attributes(self):
super().bind_attribute(0, "position")
super().bind_attribute(1, "texture_coords")
#classmethod
def get_vertex_file(cls):
return cls.__VERTEX_FILE
#classmethod
def get_fragment_file(cls):
return cls.__FRAGMENT_FILE
my fragmentShader.txt:
#version 400 core
in vec2 pass_texture_coords;
out vec4 out_color;
uniform sampler2D texture_sampler;
void main(void){
out_color = texture(texture_sampler, pass_texture_coords);
}
and my vertexShader.txt:
#version 400 core
in vec3 position;
in vec2 texture_coords;
out vec2 pass_texture_coords;
void main(void){
gl_Position = vec4(position, 1.0);
pass_texture_coords = texture_coords;
}
I created a GitHub repository if you want to check it for yourself: https://github.com/AndreasSabelfeld/pyOpenGL-3DEngine
In loader.py I needed to include:
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST)
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST)
in the load_texture method before the glTexImage2D call.
Why? I don't know.
Does it work? Yes.
I have separated the program to three different files, but I don't understand why I get error on glVertexAttribPointer on line 70. I'm using Python 3.10.8
main.py
import glfw
import Shaders
from OpenGL.GL import *
from OpenGL.GLUT import *
from Math_3d import Vector2f
class Window:
def __init__(self, width: int, height: int, title: str):
if not glfw.init():
raise Exception("glfw can not be initialized")
glfw.window_hint(glfw.CONTEXT_VERSION_MAJOR, 3)
glfw.window_hint(glfw.CONTEXT_VERSION_MINOR, 3)
glfw.window_hint(glfw.OPENGL_PROFILE, glfw.OPENGL_CORE_PROFILE)
self._win = glfw.create_window(width, height, title, None, None)
if not self._win:
glfw.terminate()
raise Exception("glfw window can not be created")
glfw.set_window_pos(self._win, 400, 200)
glfw.make_context_current(self._win)
def createshaders(self):
# Request program and shader slots from the GPU
program = glCreateProgram()
vertex = glCreateShader(GL_VERTEX_SHADER)
fragment = glCreateShader(GL_FRAGMENT_SHADER)
# Set shader sources
glShaderSource(vertex, Shaders.vertex_code)
glShaderSource(fragment, Shaders.fragment_code)
# Compile shaders
glCompileShader(vertex)
glCompileShader(fragment)
if not glGetShaderiv(vertex, GL_COMPILE_STATUS):
report_shader = glGetShaderInfoLog(vertex)
print(report_shader)
raise RuntimeError("Vertex shader compilation error")
if not glGetShaderiv(fragment, GL_COMPILE_STATUS):
report_frag = glGetShaderInfoLog(fragment)
print(report_frag)
raise RuntimeError("Fragment shader compilation error")
# Link objects to program
glAttachShader(program, vertex)
glAttachShader(program, fragment)
glLinkProgram(program)
if not glGetProgramiv(program, GL_LINK_STATUS):
print(glGetProgramInfoLog(program))
raise RuntimeError('Linking error')
# Get rid of shaders
glDetachShader(program, vertex)
glDetachShader(program, fragment)
# Make default program to run
glUseProgram(program)
# Vertex Buffer Object
# Create point vertex data
v2f_1 = Vector2f(0.0, 0.0)
# Request a buffer slot from GPU
buffer = glGenBuffers(1)
# Make this buffer the default one
glBindBuffer(GL_ARRAY_BUFFER, buffer)
strides = v2f_1.data.strides[0]
loc = glGetAttribLocation(program, 'position')
glEnableVertexAttribArray(loc)
glVertexAttribPointer(loc, 2, GL_FLOAT, False, strides, None)
# glBufferData(GL_ARRAY_BUFFER, v2f_1, v2f_1, GL_DYNAMIC_DRAW)
def renderscene(self):
while not glfw.window_should_close(self._win):
glfw.poll_events()
glClear(GL_COLOR_BUFFER_BIT)
glDrawArrays(GL_POINTS, 0, 1)
glfw.swap_buffers(self._win)
glfw.terminate()
if __name__ == '__main__':
win = Window(1024, 768, "GLFW Window")
win.createshaders() # Create and initialize shaders and initialize Vertex Buffer Object
win.renderscene() # Swap buffer and render scene
Shaders.py
vertex_code = """
attribute vec2 position;
void main()
{
gl_Position = vec4(position, 0.0, 1.0);
}
"""
fragment_code = """
void main()
{
gl_FragColor = vec4(0.0, 0.0, 0.0, 1.0);
}
"""
Math_3d.py
import numpy as np
class Vector2f:
def __init__(self, x, y):
self.data = np.array([x, y], dtype=np.float32)
if __name__ == '__main__':
vec2 = Vector2f(0.0, 0.0)
print(vec2.data)
print(type(vec2.data.strides[0]))
print(vec2.data.strides[0])
I have tried to debug the line 70, but did not get any good result while using PyCharm.
Any recommendations on this? Closest answers would be according to 61491497 and 56957118 what I am aiming for.
You're using a Core profile OpenGL Context (glfw.OPENGL_CORE_PROFILE). Therefore you have to create a Vertex Array Obejct:
class Window:
# [...]
def createshaders(self):
# [...]
v2f_1 = Vector2f(0.0, 0.0)
# Request a buffer slot from GPU
buffer = glGenBuffers(1)
glBindBuffer(GL_ARRAY_BUFFER, buffer)
strides = v2f_1.data.strides[0]
glBufferData(GL_ARRAY_BUFFER, v2f_1.data, GL_DYNAMIC_DRAW)
vao = glGenVertexArrays(1)
glBindVertexArray(vao)
loc = glGetAttribLocation(program, 'position')
glEnableVertexAttribArray(loc)
glVertexAttribPointer(loc, 2, GL_FLOAT, False, strides, None)
Additionally, you need to change either the background color or the fragment color, because you won't be able to see a black point on a black background. e.g. red:
gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0);
I'm using QSurfaceFormat to explicitly define OpenGL Core profile and set minor/major versions. The thing is, assignment of a custom surface format gives me the following error:
Traceback (most recent call last):
File "/home/artem/PycharmProjects/PolyEdit3D/PolyEdit3D/Widgets/PlyViewport.py", line 22, in initializeGL
gl.glEnable(gl.GL_COLOR_BUFFER_BIT)
File "/home/artem/.local/lib/python3.7/site-packages/OpenGL/platform/baseplatform.py", line 415, in __call__
return self( *args, **named )
File "src/errorchecker.pyx", line 58, in OpenGL_accelerate.errorchecker._ErrorChecker.glCheckError
OpenGL.error.GLError: GLError(
err = 1280,
description = b'invalid enumerant',
baseOperation = glEnable,
cArguments = (GL_COLOR_BUFFER_BIT,)
)
Traceback (most recent call last):
File "/home/artem/PycharmProjects/PolyEdit3D/PolyEdit3D/Widgets/PlyViewport.py", line 69, in paintGL
gl.glDrawElements(gl.GL_TRIANGLES, 6, gl.GL_UNSIGNED_INT, ctypes.c_void_p(0))
File "src/latebind.pyx", line 39, in OpenGL_accelerate.latebind.LateBind.__call__
File "src/wrapper.pyx", line 318, in OpenGL_accelerate.wrapper.Wrapper.__call__
File "src/wrapper.pyx", line 311, in OpenGL_accelerate.wrapper.Wrapper.__call__
File "/home/artem/.local/lib/python3.7/site-packages/OpenGL/platform/baseplatform.py", line 415, in __call__
return self( *args, **named )
File "src/errorchecker.pyx", line 58, in OpenGL_accelerate.errorchecker._ErrorChecker.glCheckError
OpenGL.error.GLError: GLError(
err = 1282,
description = b'invalid operation',
baseOperation = glDrawElements,
pyArgs = (
GL_TRIANGLES,
6,
GL_UNSIGNED_INT,
c_void_p(None),
),
cArgs = (
GL_TRIANGLES,
6,
GL_UNSIGNED_INT,
c_void_p(None),
),
cArguments = (
GL_TRIANGLES,
6,
GL_UNSIGNED_INT,
c_void_p(None),
)
)
As you can see there's an error in gl.glEnable(gl.GL_COLOR_BUFFER_BIT) and other simple things. Without specifying QSurfaceFormat everything works like a charm.
Here's my surface format class:
class GLSurfaceFormat(QtGui.QSurfaceFormat):
def __init__(self):
super(GLSurfaceFormat, self).__init__()
self.__initSurface()
def __initSurface(self):
self.setRenderableType(QtGui.QSurfaceFormat.OpenGL)
self.setMinorVersion(3)
self.setMajorVersion(4)
self.setProfile(QtGui.QSurfaceFormat.CoreProfile)
self.setColorSpace(QtGui.QSurfaceFormat.sRGBColorSpace)
self.setSwapBehavior(QtGui.QSurfaceFormat.DoubleBuffer)
Here's how I assign it:
QApplication.setAttribute(Qt.AA_UseDesktopOpenGL)
QSurfaceFormat.setDefaultFormat(GLSurfaceFormat())
app = QApplication(sys.argv)
...
Could you tell me, is there any mistake in QSurfaceFormat usage or maybe there's something else?
!UPDATE!
Here's a quick example where you can reproduce the same behavior:
from PySide2 import QtCore, QtGui, QtWidgets
from OpenGL import GL as gl
from OpenGL.GL.shaders import compileProgram, compileShader
import numpy as np
import ctypes
import sys
import glm
class PlyViewportWidget(QtWidgets.QOpenGLWidget):
def __init__(self):
super(PlyViewportWidget, self).__init__(parent=None)
self.vao = None
self.vbo = None
self.ebo = None
self.shaderProg = None
self.isWireframe = False
def initializeGL(self):
gl.glEnable(gl.GL_COLOR_BUFFER_BIT)
gl.glClearColor(0.4, 0.4, 0.4, 1)
with open("fragment.glsl", 'r') as f:
fragment = compileShader(f.read(), gl.GL_FRAGMENT_SHADER)
with open("vertex.glsl", "r") as f:
vertex = compileShader(f.read(), gl.GL_VERTEX_SHADER)
self.shaderProg = compileProgram(vertex, fragment)
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=ctypes.c_float
)
indices = np.array(
[
0, 1, 3,
1, 2, 3
], dtype=ctypes.c_uint
)
self.vbo = gl.glGenBuffers(1)
self.ebo = gl.glGenBuffers(1)
gl.glBindBuffer(gl.GL_ARRAY_BUFFER, self.vbo)
gl.glBufferData(gl.GL_ARRAY_BUFFER, vertices.nbytes, vertices, gl.GL_STATIC_DRAW)
gl.glBindBuffer(gl.GL_ELEMENT_ARRAY_BUFFER, self.ebo)
gl.glBufferData(gl.GL_ELEMENT_ARRAY_BUFFER, indices.nbytes, indices, gl.GL_STATIC_DRAW)
gl.glEnableVertexAttribArray(0)
gl.glVertexAttribPointer(0, 3, gl.GL_FLOAT, gl.GL_FALSE, 3 * ctypes.sizeof(ctypes.c_float), ctypes.c_void_p(0))
gl.glBindBuffer(gl.GL_ARRAY_BUFFER, 0)
gl.glBindVertexArray(0)
gl.glUseProgram(self.shaderProg)
def paintGL(self):
gl.glClearColor(0.4, 0.4, 0.4, 1.0)
gl.glClear(gl.GL_COLOR_BUFFER_BIT)
trans = self.getTransformMatrix()
#u_transform = gl.glGetUniformLocation(self.shaderProg, "u_transform")
#gl.glUniformMatrix4fv(u_transform, 1, gl.GL_FALSE, u_transform)
gl.glBindVertexArray(self.vao)
gl.glDrawElements(gl.GL_TRIANGLES, 6, gl.GL_UNSIGNED_INT, ctypes.c_void_p(0))
def resizeGL(self, w:int, h:int):
gl.glViewport(0, 0, w, h)
def keyPressEvent(self, event:QtGui.QKeyEvent):
self.makeCurrent()
if event.key() == QtCore.Qt.Key_Z and not self.isWireframe:
gl.glPolygonMode(gl.GL_FRONT_AND_BACK, gl.GL_LINE)
self.isWireframe = True
self.update()
elif event.key() == QtCore.Qt.Key_Z and self.isWireframe:
gl.glPolygonMode(gl.GL_FRONT_AND_BACK, gl.GL_FILL)
self.isWireframe = False
self.update()
event.accept()
def getTransformMatrix(self):
return glm.rotate(glm.mat4(1.0), glm.radians(90.0), glm.vec3(1.0, 0.0, 0.0))
class GLSurfaceFormat(QtGui.QSurfaceFormat):
def __init__(self):
super(GLSurfaceFormat, self).__init__()
self.__initSurface()
def __initSurface(self):
self.setRenderableType(QtGui.QSurfaceFormat.OpenGL)
self.setMinorVersion(3)
self.setMajorVersion(4)
self.setProfile(QtGui.QSurfaceFormat.CoreProfile)
self.setColorSpace(QtGui.QSurfaceFormat.sRGBColorSpace)
self.setSwapBehavior(QtGui.QSurfaceFormat.DoubleBuffer)
if __name__ == '__main__':
QtWidgets.QApplication.setAttribute(QtCore.Qt.AA_UseDesktopOpenGL)
# Uncomment to get this damn error!!!
#QtGui.QSurfaceFormat.setDefaultFormat(GLSurfaceFormat())
app = QtWidgets.QApplication(sys.argv)
window = PlyViewportWidget()
window.show()
sys.exit(app.exec_())
vertex.glsl
#version 330 core
layout (location = 0) in vec3 aPos;
void main()
{
gl_Position = vec4(aPos, 1.0);
}
fragment.glsl
#version 330 core
out vec4 FragColor;
void main()
{
FragColor = vec4(1.0f, 0.5f, 0.2f, 1.0f);
}
GL_COLOR_BUFFER_BIT is not a valid enumeration constant for glEnable, but it is a proper argument for glClear.
Invoke glClear, after the clear color is set by glClearColor:
class PlyViewportWidget(QtWidgets.QOpenGLWidget):
# [...]
def initializeGL(self):
#gl.glEnable(gl.GL_COLOR_BUFFER_BIT) <---- DELETE
gl.glClearColor(0.4, 0.4, 0.4, 1)
gl.glClear(gl.GL_COLOR_BUFFER_BIT) # <---- ADD
You missed to create the named Vertex Array Object:
class PlyViewportWidget(QtWidgets.QOpenGLWidget):
# [...]
def initializeGL(self):
# [...]
self.vao = gl.glGenVertexArrays(1) # <---
gl.glBindVertexArray(self.vao) # <---
self.vbo = gl.glGenBuffers(1)
self.ebo = gl.glGenBuffers(1)
gl.glBindBuffer(gl.GL_ARRAY_BUFFER, self.vbo)
gl.glBufferData(gl.GL_ARRAY_BUFFER, vertices.nbytes, vertices, gl.GL_STATIC_DRAW)
gl.glBindBuffer(gl.GL_ELEMENT_ARRAY_BUFFER, self.ebo)
gl.glBufferData(gl.GL_ELEMENT_ARRAY_BUFFER, indices.nbytes, indices, gl.GL_STATIC_DRAW)
gl.glEnableVertexAttribArray(0)
gl.glVertexAttribPointer(0, 3, gl.GL_FLOAT, gl.GL_FALSE, 3 * ctypes.sizeof(ctypes.c_float), ctypes.c_void_p(0))
gl.glBindBuffer(gl.GL_ARRAY_BUFFER, 0)
gl.glBindVertexArray(0)
I am running through a cumulation of OpenGL shader tutorials and mixing and matching stuff, trying to get a custom shader implemented. I have the following Python code and traceback:
import sys
from PyQt5.QtWidgets import QApplication, QMainWindow, QOpenGLWidget
from PyQt5.QtCore import Qt
from OpenGL.GL import (
glLoadIdentity, glTranslatef, glRotatef,
glClear, glBegin, glEnd,
glColor3fv, glVertex3fv,
GL_COLOR_BUFFER_BIT, GL_DEPTH_BUFFER_BIT,
GL_QUADS, GL_LINES,
shaders, GL_VERTEX_SHADER, GL_FRAGMENT_SHADER
)
from OpenGL.GLU import gluPerspective
class mainWindow(QMainWindow): #Main class.
def keyPressEvent(self, event): #This is the keypress detector.
try:
key = event.key()
except:
key = -1
#print(key)
if key == 16777216:
exit()
vertices = [
(-1, 1, 0),
(1, 1, 0),
(1, -1, 0),
(-1, -1, 0)
]
wires = [
(0, 1),
(1, 2),
(2, 3),
(0, 3)
]
facets = [
(0, 1, 2, 3)
]
zoomLevel = -5
rotateDegreeH = 0
rotateDegreeV = -45
vertShaderCode = """#version 120
void main() {
gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex;
}"""
fragShaderCode = """#version 120
void main() {
gl_FragColor = vec4( 0, 1, 0, 1 );
}"""
def __init__(self):
super(mainWindow, self).__init__()
self.sizeX = 700 #Variables used for the setting of the size of everything
self.sizeY = 600
self.setGeometry(0, 0, self.sizeX + 50, self.sizeY) #Set the window size
#make shaders
VERTEX_SHADER = shaders.compileShader(self.vertShaderCode, GL_VERTEX_SHADER)
FRAGMENT_SHADER = shaders.compileShader(self.fragShaderCode, GL_FRAGMENT_SHADER)
self.shader = shaders.compileProgram(VERTEX_SHADER,FRAGMENT_SHADER)
self.openGLWidget = QOpenGLWidget(self) #Create the GLWidget
self.openGLWidget.setGeometry(0, 0, self.sizeX, self.sizeY)
self.openGLWidget.initializeGL()
self.openGLWidget.resizeGL(self.sizeX, self.sizeY) #Resize GL's knowledge of the window to match the physical size?
self.openGLWidget.paintGL = self.paintGL #override the default function with my own?
def nav(self, hVal = 0, vVal = 0, zVal = 0):
self.zoomLevel += zVal
self.rotateDegreeH += hVal
self.rotateDegreeV += vVal
self.openGLWidget.update()
def paintGL(self):
#This function uses shape objects, such as cube() or mesh(). Shape objects require the following:
#a list named 'vertices' - This list is a list of points, from which edges and faces are drawn.
#a list named 'wires' - This list is a list of tuples which refer to vertices, dictating where to draw wires.
#a list named 'facets' - This list is a list of tuples which refer to vertices, ditating where to draw facets.
#a bool named 'render' - This bool is used to dictate whether or not to draw the shape.
#a bool named 'drawWires' - This bool is used to dictate whether wires should be drawn.
#a bool named 'drawFaces' - This bool is used to dictate whether facets should be drawn.
shaders.glUseProgram(self.shader)
glLoadIdentity()
gluPerspective(45, self.sizeX / self.sizeY, 0.1, 110.0) #set perspective?
glTranslatef(0, 0, self.zoomLevel) #I used -10 instead of -2 in the PyGame version.
glRotatef(self.rotateDegreeV, 1, 0, 0) #I used 2 instead of 1 in the PyGame version.
glRotatef(self.rotateDegreeH, 0, 0, 1)
glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT)
glBegin(GL_LINES)
for w in self.wires:
for v in w:
glVertex3fv(self.vertices[v])
glEnd()
glBegin(GL_QUADS)
for f in self.facets:
for v in f:
glVertex3fv(self.vertices[v])
glEnd()
app = QApplication([])
window = mainWindow()
window.show()
sys.exit(app.exec_())
C:\Users\ccronk22\Documents\Python\glShaders>pygl002.py
Traceback (most recent call last):
File "C:\Users\ccronk22\AppData\Local\Programs\Python\Python38\lib\site-packages\OpenGL\latebind.py", line 43, in __call__
return self._finalCall( *args, **named )
TypeError: 'NoneType' object is not callable
During handling of the above exception, another exception occurred:
Traceback (most recent call last):
File "C:\Users\ccronk22\Documents\Python\glShaders\pyGL002.py", line 109, in <module>
window = mainWindow()
File "C:\Users\ccronk22\Documents\Python\glShaders\pyGL002.py", line 59, in __init__
VERTEX_SHADER = shaders.compileShader(self.vertShaderCode, GL_VERTEX_SHADER)
File "C:\Users\ccronk22\AppData\Local\Programs\Python\Python38\lib\site-packages\OpenGL\GL\shaders.py", line 228, in compileShader
shader = glCreateShader(shaderType)
File "C:\Users\ccronk22\AppData\Local\Programs\Python\Python38\lib\site-packages\OpenGL\latebind.py", line 46, in __call__
self._finalCall = self.finalise()
File "C:\Users\ccronk22\AppData\Local\Programs\Python\Python38\lib\site-packages\OpenGL\extensions.py", line 242, in finalise
raise error.NullFunctionError(
OpenGL.error.NullFunctionError: Attempt to call an undefined alternate function (glCreateShader, glCreateShaderObjectARB), check for bool(glCreateShader) before calling
C:\Users\ccronk22\Documents\Python\glShaders>
The above code works well if you comment out the compileShader, compileProgram, and useProgram lines. It produces a white square, viewed from an above angle:
def __init__(self):
super(mainWindow, self).__init__()
self.sizeX = 700 #Variables used for the setting of the size of everything
self.sizeY = 600
self.setGeometry(0, 0, self.sizeX + 50, self.sizeY) #Set the window size
#make shaders
#VERTEX_SHADER = shaders.compileShader(self.vertShaderCode, GL_VERTEX_SHADER)
#FRAGMENT_SHADER = shaders.compileShader(self.fragShaderCode, GL_FRAGMENT_SHADER)
#self.shader = shaders.compileProgram(VERTEX_SHADER,FRAGMENT_SHADER)
[…]
def paintGL(self):
#This function uses shape objects, such as cube() or mesh(). Shape objects require the following:
#a list named 'vertices' - This list is a list of points, from which edges and faces are drawn.
#a list named 'wires' - This list is a list of tuples which refer to vertices, dictating where to draw wires.
#a list named 'facets' - This list is a list of tuples which refer to vertices, ditating where to draw facets.
#a bool named 'render' - This bool is used to dictate whether or not to draw the shape.
#a bool named 'drawWires' - This bool is used to dictate whether wires should be drawn.
#a bool named 'drawFaces' - This bool is used to dictate whether facets should be drawn.
#shaders.glUseProgram(self.shader)
Some additional information:
I am attempting to make the code from http://pyopengl.sourceforge.net/context/tutorials/shader_1.html work in the PyQt5 context, with only the OpenGL and PyQt5 APIs. The tutorial was written in Python2. I am using Python3.
So, what am I doing wrong with my shader compilation? Is it in the Python or GLSL?
To compile and link the shader you need a valid and current OpenGL Context. Note the OpenGL context have to be current when any OpenGL instruction is invoked.
QOpenGLWidget provides the virtual methods
def initializeGL ()
def paintGL ()
def resizeGL (w, h)
where the OpenGL context is active. This methods are callbacks and invoked by Qts event handling. Don't call them in your code.
Create the shader in the initializeGL callback:
class mainWindow(QMainWindow):
def __init__(self):
super(mainWindow, self).__init__()
self.sizeX = 700 #Variables used for the setting of the size of everything
self.sizeY = 600
self.setGeometry(0, 0, self.sizeX + 50, self.sizeY) #Set the window size
self.openGLWidget = QOpenGLWidget(self) #Create the GLWidget
self.openGLWidget.setGeometry(0, 0, self.sizeX, self.sizeY)
self.openGLWidget.resizeGL(self.sizeX, self.sizeY) #Resize GL's knowledge of the window to match the physical size?
self.openGLWidget.initializeGL = self.initializeGL
self.openGLWidget.paintGL = self.paintGL #override the default function with my own?
self.shader = None
def nav(self, hVal = 0, vVal = 0, zVal = 0):
self.zoomLevel += zVal
self.rotateDegreeH += hVal
self.rotateDegreeV += vVal
self.openGLWidget.update()
def initializeGL(self):
#make shaders
VERTEX_SHADER = shaders.compileShader(self.vertShaderCode, GL_VERTEX_SHADER)
FRAGMENT_SHADER = shaders.compileShader(self.fragShaderCode, GL_FRAGMENT_SHADER)
self.shader = shaders.compileProgram(VERTEX_SHADER,FRAGMENT_SHADER)
def paintGL(self):
glUseProgram(self.shader)
# [...]
(The import of glUseProgram is missing in your code)
I cannot figure this out for the life of me. I need a second pair of eyes ... or a better brain. I am trying to get this "Hello Triangle" python example working. I've been translating it from a c tutorial. However, I keep getting this error no matter what I do.
Traceback (most recent call last):
File ".../demo/tester.py", line 107, in <module>
if __name__ == '__main__': main()
File ".../demo/tester.py", line 87, in main
glDrawArrays(GL_TRIANGLES, 0, 3)
File ".../venv/lib/python3.6/site packages/OpenGL/platform/baseplatform.py", line 402, in __call__
return self( *args, **named )
File ".../venv/lib/python3.6/site-packages/OpenGL/error.py", line 232, in glCheckError
baseOperation = baseOperation,
OpenGL.error.GLError: GLError(
err = 1282,
description = b'invalid operation',
baseOperation = glDrawArrays,
cArguments = (GL_TRIANGLES, 0, 3)
My code is below. I am running on a Mac so you'll notice some things in there that might not be needed for PC. Everything works fine up until the glDrawArrays. I know that some of the functions between openGL for C vs python using pyOpenGL are a bit different. I've been cross referencing the documentation to make sure I am not trying write like a C programmer in Python.
try:
from AppKit import NSApp, NSApplication
except:
pass
import sys
import cyglfw3 as glfw
import numpy as np
from OpenGL.GL import *
from OpenGL.GLUT import *
from OpenGL.arrays import vbo as glvbo
def main():
glfw.Init()
glfw.WindowHint(glfw.CONTEXT_VERSION_MAJOR, 3)
glfw.WindowHint(glfw.CONTEXT_VERSION_MINOR, 3)
glfw.WindowHint(glfw.OPENGL_PROFILE, glfw.OPENGL_CORE_PROFILE)
glfw.WindowHint(glfw.OPENGL_FORWARD_COMPAT, GL_TRUE)
window = glfw.CreateWindow(800, 600, 'LearnOpenGL', None)
if window is None:
glfw.Terminate()
exit()
glfw.MakeContextCurrent(window)
glfw.SetFramebufferSizeCallback(window, framebuffer_size_callback())
# Setting up a vertex buffer
verts = glvbo.VBO(
np.array([[0.0, 0.5, 0.0], [0.5, -0.5, 0.0], [-0.5, -0.5, 0.0]], 'f')
)
vbo = glGenBuffers(1)
glBindBuffer(GL_ARRAY_BUFFER, vbo)
glBufferData(GL_ARRAY_BUFFER, verts.size, verts, GL_STATIC_DRAW)
vao = glGenVertexArrays(1)
glBindVertexArray(vao)
glEnableVertexAttribArray(vao)
glBindBuffer(GL_ARRAY_BUFFER, vbo)
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 0, None)
vertex_shader = """
#version 330 core
in vec3 vp;
void main() {
gl_Position = vec4(vp, 1.0);
}"""
fragment_shader = """
#version 330 core
out vec4 frag_color;
void main() {
frag_color = vec4(0.5, 0.0, 0.5, 1.0);
}"""
vs = glCreateShader(GL_VERTEX_SHADER)
glShaderSource(vs, vertex_shader)
glCompileShader(vs)
if glGetShaderiv(vs, GL_COMPILE_STATUS) != GL_TRUE:
raise Exception("vertex shader did not compile")
fs = glCreateShader(GL_FRAGMENT_SHADER)
glShaderSource(fs, fragment_shader)
glCompileShader(fs)
if glGetShaderiv(fs, GL_COMPILE_STATUS) != GL_TRUE:
raise Exception("fragment shader did not compile")
sp = glCreateProgram()
glAttachShader(sp, fs)
glAttachShader(sp, vs)
glLinkProgram(sp)
if glGetProgramiv(sp, GL_LINK_STATUS) != GL_TRUE:
raise Exception("program did not link")
while not glfw.WindowShouldClose(window):
processInput(window)
glClearColor(0.0, 0.0, 0.0, 1.0)
glClear(GL_COLOR_BUFFER_BIT)
glUseProgram(sp)
glBindVertexArray(vao)
glDrawArrays(GL_TRIANGLES, 0, 3) # This is where things happen
glfw.PollEvents()
glfw.SwapBuffers(window)
glfw.Terminate()
exit()
def framebuffer_size_callback():
glViewport(0, 0, 800, 600)
def processInput(window):
if glfw.GetKey(window, glfw.KEY_ESCAPE) == glfw.PRESS:
glfw.SetWindowShouldClose(window, True)
if __name__ == '__main__': main()
All I want to do at the end of the day, is draw a triangle :(
I determined there were 2 issues.
As BDL mentioned the glEnableVertexAttribArray was not setup appropriately. It needed an index and not a vertex array object.
The glBufferData size was incorrect as the size needed to be provided in bytes. A quick search online revealed that (numpy.size * numpy.itemsize) returns the size of the array.
With these 2 things done... I have a triangle.