I have a simple PyQt OpenGL widget. It fails to render. When the fragment shader is #version 330 in vec2 f_position; out vec4 fragColor; void main() { fragColor = vec4(f_position.x, f_position.y, 0.5, 1.0); } then this in paintGL() if not self.vao.bind(): print ("paintGL could not bind vao: "+ str(glGetError())) prints paintGL could not bind vao: 0 This is weird: couldn't bind but error is 0? When I use the full fragment shader in the code below, I get paintGL could not bind vao: 1282 Either way, the only thing that renders is the glClearColor value. Full code: from PyQt5.QtWidgets import * from PyQt5.QtGui import * from PyQt5.QtCore import * from PyQt5.QtOpenGL import * from OpenGL.GL import * import math import numpy.linalg VERTEX_SHADER = """ #version 330 in vec2 v_position; out vec2 f_position; void main() { f_position = v_position; gl_Position = vec4(v_position, 0.0, 1.0); } """ FRAGMENT_SHADER = """ #version 330 in vec2 f_position; uniform vec3 u_base_colour; uniform vec3 u_surface_focus; // centre of globe to (latitude,longitude,altitude=1) NOT scaled by radius uniform vec3 u_right_unit; uniform vec3 u_up_unit; uniform vec3 u_globe_position; uniform float u_globe_radius; out vec4 fragColor; vec3 sphericalToCartesian(float polar_angle, float azimuthal_angle) // latitude, longitude { float cos_polar = cos(polar_angle); float sin_polar = sin(polar_angle); float cos_azimuthal = cos(azimuthal_angle); float sin_azimuthal = sin(azimuthal_angle); float x = cos_polar * cos_azimuthal; float y = cos_polar * sin_azimuthal; float z = sin_polar; return vec3(x, y, z); } // returns (latitude,longitude,altitude) vec3 cartesianToSpherical(vec3 cartesian) { float x = cartesian.x; float y = cartesian.y; float z = cartesian.z; // Calculate altitude (distance from origin) float altitude = length(cartesian); // Handle the case when point is at the origin if (altitude < 1e-6) return vec3(0.0, 0.0, 0.0); // Calculate polar angle (latitude) float polar_angle = asin(z / altitude); // Calculate azimuthal angle (longitude) float azimuthal_angle = atan(y, x); // Ensure azimuthal_angle is in the range [0, 2π] if (azimuthal_angle < 0.0) azimuthal_angle += 2.0 * 3.14159265359; return vec3(polar_angle, azimuthal_angle, altitude); } float raySphereIntersect (vec3 ray_origin, vec3 ray_direction, vec3 sphere_centre, float radius) { float a = dot (ray_direction, ray_direction); vec3 centre_to_ray_origin = ray_origin - sphere_centre; float b = 2.0 * dot(ray_direction, centre_to_ray_origin); float c = dot(centre_to_ray_origin, centre_to_ray_origin) - (radius * radius); float discriminant = b*b - 4.0*a*c; if (discriminant < 0.0) return -1.0; else return (-b - sqrt(discriminant))/(2.0*a); } void main() { vec3 camera_coordinate = f_position.x*u_right_unit + f_position.y*u_up_unit; // 1.1 is arbitrary, but >1 to prevent rounding error edge case. vec3 camera_origin = (1.1*u_globe_radius) * u_surface_focus; vec3 ray_direction = -u_surface_focus; float distance_to_intersection = raySphereIntersect ( camera_origin + camera_coordinate*u_globe_radius, ray_direction, vec3(0.0, 0.0, 0.0), // We are in local space u_globe_radius); if (distance_to_intersection < 0.0) fragColor = vec4(0.0, 0.0, 0.9, 1.0); else { vec3 surface_intersection = camera_origin + distance_to_intersection * ray_direction; // TODO invert the rotation given time vec3 local_surface = cartesianToSpherical(surface_intersection); // geological coordinates -> local coordinates over time // invert this to get geological coordinates vec3 geo_surface = local_surface; vec3 lat_lon_alt = cartesianToSpherical(geo_surface); vec2 geo_texture_coordinates = vec2( lat_lon_alt.x / (2.0 * 3.14159265359), (1.0 + lat_lon_alt.y / 3.14159265359) / 2.0); fragColor = vec4(geo_texture_coordinates.x, geo_texture_coordinates.y, u_base_colour.z, 1.0); } } """ def sphericalToCartesian(polar_angle, azimuthal_angle): # latitude, longitude cos_polar = math.cos(polar_angle); sin_polar = math.sin(polar_angle); cos_azimuthal = math.cos(azimuthal_angle); sin_azimuthal = math.sin(azimuthal_angle); return [ cos_polar * cos_azimuthal, cos_polar * sin_azimuthal, sin_polar] class SquareGLWidget(QOpenGLWidget): def __init__(self, parent=None): super().__init__(parent) self.shaderProgram = None self.u_base_colour = [1.0, 0.0, 1.0] self.u_globe_position = [0.0, 0.0, 0.0] self.surface_focus_lat_lon = [0.0, 0.0] self.u_globe_radius = 6378 # Earth, km def initializeGL(self): self.shaderProgram = QOpenGLShaderProgram() if not self.shaderProgram.addShaderFromSourceCode(QOpenGLShader.Vertex, VERTEX_SHADER): print("Vertex shader compilation failed:", self.shaderProgram.log()) return if not self.shaderProgram.addShaderFromSourceCode(QOpenGLShader.Fragment, FRAGMENT_SHADER): print("Fragment shader compilation failed:", self.shaderProgram.log()) return if not self.shaderProgram.link(): print("Shader program linking failed:", self.shaderProgram.log()) return print("Shader program linked successfully") self.vao = QOpenGLVertexArrayObject() if not self.vao.create(): print("Could not create VAO") return self.vao.bind() self.vbo = QOpenGLBuffer() if not self.vbo.create(): print("Could not create VAO") return self.vbo.bind() vertices = [-1.0, -1.0, 1.0, -1.0, -1.0, 1.0, 1.0, 1.0] data = numpy.array(vertices, dtype=numpy.float32) self.vbo.allocate(data, data.nbytes) self.shaderProgram.bind() attr_loc = self.shaderProgram.attributeLocation("v_position") print (f"{attr_loc=}") # is 0 self.shaderProgram.enableAttributeArray(attr_loc) self.shaderProgram.setAttributeBuffer(attr_loc, GL_FLOAT, 0, 2, 2 * sizeof(GLfloat)) self.shaderProgram.release() self.vbo.release() self.vao.release() glEnable(GL_DEPTH_TEST) glClearColor(0.5, 0.0, 0.5, 1.0) def paintGL(self): if not QOpenGLContext.currentContext(): print ("No currentContext") if not self.context(): print ("Not self.context") if not self.context().isValid(): print("Invalid self.context") return surface_focus = sphericalToCartesian(*self.surface_focus_lat_lon) # has unit length right_unit = numpy.linalg.cross([0.0,1.0,0.0], surface_focus) up_unit = numpy.linalg.cross(surface_focus, right_unit) glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT) if not self.shaderProgram.isLinked(): print ("paintGL: shader is not linked") if not self.shaderProgram.bind(): print ("paintGL could not bind shader") self.shaderProgram.setUniformValue("u_base_colour", *self.u_base_colour) self.shaderProgram.setUniformValue("u_globe_position", *self.u_globe_position) self.shaderProgram.setUniformValue("u_surface_focus", *surface_focus) self.shaderProgram.setUniformValue("u_right_unit", *right_unit) self.shaderProgram.setUniformValue("u_up_unit", *up_unit) self.shaderProgram.setUniformValue("u_globe_radius", self.u_globe_radius) if not self.vao.bind(): print ("paintGL could not bind vao "+ str(glGetError())) # 1282 self.shaderProgram.release() return else: print ("paintGL could bind vao") glDrawArrays(GL_TRIANGLE_STRIP, 0, 4) self.vao.release() self.shaderProgram.release() def resizeGL(self, w, h): side = min(w, h) x = (w - side) // 2 y = (h - side) // 2 glViewport(x, y, side, side) What am I doing wrong? Continue reading...