PyOpenGL seems not to work with trimesh .obj through glDrawElementsInstanced
18:51 11 Dec 2025

I've been stuck on this problem for at least 4 days and I have almost tried everything. I have .obj files, I load them through trimesh.load and then make up the vertex data and indices, but PyOpenGL seems not to draw correctly the mesh through glDrawElementsInstanced (the "Instanced" part works just fine).

Mesh Loader from file:

    def _load_mesh_from_file(self, path):
        mesh = trimesh.load(path, force="mesh")
        trimesh.repair.fill_holes(mesh)
        trimesh.repair.fix_winding(mesh)

        # vertices_pos = data[:, 0:3]
        # vertices_uv = data[:, 3:5]       # texcoord
        # vertices_normal = data[:, 5:8]    # normal
        # vertices_tangent = data[:, 8:11] # tangent
        # vertices_bitangent = data[:, 11:14] # bitangent

        faces = mesh.faces

        vertex_pos = mesh.vertices
        vertex_uv = mesh.visual.uv
        vertex_normal = mesh.vertex_normals
        vertex_tangent, vertex_bitangent = self._load_tangent_bitangent(vertex_pos, vertex_uv, faces)

        vertex_data = []

        for i in range(len(vertex_pos)):
            vertex_data.extend([
                *vertex_pos[i],
                *vertex_uv[i],
                *vertex_normal[i],
                *vertex_tangent[i],
                *vertex_bitangent[i],
            ])
        
        return np.ascontiguousarray(vertex_data, dtype=np.float32), np.ascontiguousarray(faces, dtype=np.uint32) // this is what makes up mesh_data

Mesh Instance:

    def __init__(self,
                 mesh_data: list | tuple,
                 name: str="",
                 position: list | np.ndarray=[0, 0, 0],
                 eulers: list | np.ndarray=[0, 0, 0],
                 scale: list | np.ndarray=[1, 1, 1],
                ):

        self.objects: list = []

        self.position = np.array(position, dtype=np.float32)
        self.eulers = np.array(eulers, dtype=np.float32)
        self.scale = np.array(scale, dtype=np.float32)

        self.name = name

        self.max_instances = 1000

        self.mesh_data = mesh_data

        vertex_data = mesh_data[0]
        indices = mesh_data[1]

        self.vertex_data = np.ascontiguousarray(vertex_data, dtype=np.float32)
        self.indices_data = np.ascontiguousarray(indices, dtype=np.uint32)

        self.vertex_count = vertex_data.shape[0]
        self.index_count = len(indices)

        stride = 14 * 4

        self.vao = glGenVertexArrays(1)
        glBindVertexArray(self.vao)

        vertex_buffer = np.array(self.vertex_data, dtype=np.float32).reshape(-1, 14)

        self.vbo = glGenBuffers(1)
        glBindBuffer(GL_ARRAY_BUFFER, self.vbo)
        glBufferData(GL_ARRAY_BUFFER, vertex_buffer.nbytes, vertex_buffer, GL_STATIC_DRAW)

        index_buffer = np.array(self.indices_data, dtype=np.uint32)

        self.ebo = glGenBuffers(1)
        glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, self.ebo)
        glBufferData(GL_ELEMENT_ARRAY_BUFFER, index_buffer.nbytes, index_buffer, GL_STATIC_DRAW)

        # Posizione
        glEnableVertexAttribArray(0)
        glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, stride, ctypes.c_void_p(0))
        # UV
        glEnableVertexAttribArray(1)
        glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, stride, ctypes.c_void_p(12))
        # Normale
        glEnableVertexAttribArray(2)
        glVertexAttribPointer(2, 3, GL_FLOAT, GL_FALSE, stride, ctypes.c_void_p(20))
        # Tangente
        glEnableVertexAttribArray(3)
        glVertexAttribPointer(3, 3, GL_FLOAT, GL_FALSE, stride, ctypes.c_void_p(32))
        # Bitangente
        glEnableVertexAttribArray(4)
        glVertexAttribPointer(4, 3, GL_FLOAT, GL_FALSE, stride, ctypes.c_void_p(44))

        self._gpu_indices = self.indices_data
        self._gpu_vertices = self.vertex_data

        self.max_instances = getattr(self, "max_instances", 1000)
        self.instance_vbo = glGenBuffers(1)
        glBindBuffer(GL_ARRAY_BUFFER, self.instance_vbo)
        glBufferData(GL_ARRAY_BUFFER, self.max_instances * 16 * 4, None, GL_DYNAMIC_DRAW)

        for i in range(4):
            glEnableVertexAttribArray(5 + i)
            glVertexAttribPointer(
                5 + i,
                4,
                GL_FLOAT,
                GL_FALSE,
                64,
                ctypes.c_void_p(i * 16)
            )
            glVertexAttribDivisor(5 + i, 1)

        glBindBuffer(GL_ARRAY_BUFFER, 0)
        glBindVertexArray(0)

Draw call:

    def draw(self, instance_count):
        if instance_count == 0:
            return

        self._bind_vao()

        self.shader.use()

        if engine_config.SHOW_WIRE_FRAME:
            glPolygonMode(GL_FRONT_AND_BACK, GL_LINE)

        glDrawElementsInstanced(
            GL_TRIANGLES,
            self.index_count,
            GL_UNSIGNED_INT,
            None,
            instance_count
        )

        # glDrawArraysInstanced(
        #     GL_TRIANGLES,
        #     0,
        #     self.vertex_count,
        #     instance_count
        # )

        if engine_config.SHOW_WIRE_FRAME:
            glPolygonMode(GL_FRONT_AND_BACK, GL_FILL)

        glBindVertexArray(0)
# glDrawArraysInstanced( GL_LINE_STRIP, 0, self.vertex_count, instance_count )

Cube Wireframe
Cube

python opengl pyopengl trimesh