Visualizing a 3D NumPy array with PyOpenGL - python

I want to create a PyOpenGL/QtOpenGL widget that will allow me to visualize an arbitrary NumPy 3D matrix, not unlike the following Hinton diagram envisioned as a "cube of cubes" instead of a "square of squares":
I'm having a bit of a rough time with OpenGL though. Here is my code thus far:
from OpenGL.GL import *
from OpenGL.GLUT import *
from PyQt4 import QtGui, QtOpenGL
import numpy as np
action_keymap = {
# 'a': lambda: glTranslate(-1, 0, 0),
# 'd': lambda: glTranslate( 1, 0, 0),
# 'w': lambda: glTranslate( 0, 1, 0),
# 's': lambda: glTranslate( 0,-1, 0),
'a': lambda: glRotate(-5, 0, 1, 0),
'd': lambda: glRotate( 5, 0, 1, 0),
# 'W': lambda: glRotate(-5, 1, 0, 0),
# 'S': lambda: glRotate( 5, 1, 0, 0),
}
ARRAY = np.ones([3,3,3])
class GLWidget(QtOpenGL.QGLWidget):
def paintGL(self):
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)
for idx, value in np.ndenumerate(ARRAY):
rel_pos = np.array(idx)/np.max(ARRAY.shape)
glTranslate(* rel_pos)
glutSolidCube(0.9/np.max(ARRAY.shape))
glTranslate(*-rel_pos)
def resizeGL(self, w, h):
glLoadIdentity()
glRotate(35,1,0,0)
glRotate(45,0,1,0)
def initializeGL(self):
glClearColor(0.1, 0.1, 0.3, 1.0)
def keyPressEvent(self, event):
action = action_keymap.get(str(event.text()))
if action:
action()
self.updateGL()
def mousePressEvent(self, event):
super().mousePressEvent(event)
self.press_point = event.pos()
def mouseMoveEvent(self, event):
super().mouseMoveEvent(event)
motion = event.pos()-self.press_point
self.press_point = event.pos()
glRotate(motion.x(),0,1,0)
glRotate(motion.y(),1,0,0)
self.updateGL()
if __name__ == '__main__':
app = QtGui.QApplication(sys.argv)
w = GLWidget()
w.show()
sys.exit(app.exec_())
My problems are as follows:
1) Lighting. I've been reading up on lighting and materials, but I cannot seem to get a simple light somewhere giving the shape some clarity. I'd like the simplest, most basic possible light to be able to distinguish the squares instead of them being all rendered as pure white on all sides. I know how to change the color, but it doesn't alleviate the problem. What is the simplest light I can shine on this lattice to get some clarity on the subcomponents?
2) It is slow. I'll work out the math to achieve proper positioning and resizing of squares down the line, but I was wondering if there was a way to vectorize the process (after all, it's only turning the index into a translation and the value into a cube size for every element in the array). Should I write an extension in cpp, wrap my code with ctypes, or is there a way to outsource the work to OpenGL explicitly? What is the standard way to send a repetitive task to OpenGL from Python?

This task is perfectly suited for Instancing. With instancing an object can be rendered multiple times.
In this case instancing is used to render a cube for ach element of a 3d NumPy array.
Lets assume we've the following 3D array (array3d) of random values in the range [0, 1]:
shape = [5, 4, 6]
number_of = shape[0] * shape[1] * shape[2]
array3d = np.array(np.random.rand(number_of), dtype=np.float32).reshape(shape)
For each element of the array an instance of a mesh (cube) has to be rendered:
e.g.
number_of = array3d.shape[0] * array3d.shape[1] * array3d.shape[2]
glDrawElementsInstanced(GL_TRIANGLES, self.__no_indices, GL_UNSIGNED_INT, None, number_of)
The array can be loaded to a 3D texture (glTexImage3D):
glActiveTexture(GL_TEXTURE1)
tex3DObj = glGenTextures(1)
glBindTexture(GL_TEXTURE_3D, tex3DObj)
glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_MAX_LEVEL, 0)
glTexImage3D(GL_TEXTURE_3D, 0, GL_R16F, *array3d.shape, 0, GL_RED, GL_FLOAT, array3d)
In the vertex shader for a single cube, a instance transformation matrix can be computes by the dimension of the 3D texture (which is equal the shape of the 3D array) and the gl_InstanceID of the element cube.
The element cube is further scaled by the value of the element in the 3D texture.
Assuming a vertex shader with a §D texture sampler uniform u_array3D and a vertex coordinate attribute a_pos:
in vec3 a_pos;
uniform sampler3D u_array3D;
The dimension of the texture can be get by textureSize:
ivec3 dim = textureSize(u_array3D, 0);
With the dimension and the gl_InstanceID, the index of the element can be computed:
ivec3 inx = ivec3(0);
inx.z = gl_InstanceID / (dim.x * dim.y);
inx.y = (gl_InstanceID - inx.z * dim.x * dim.y) / dim.x;
inx.x = gl_InstanceID - inx.z * dim.x * dim.y - inx.y * dim.x;
and the value of the element can be fetched (texelFetch):
float value = texelFetch(u_array3D, inx, 0).x;
Finally a instance transformation matrix dependent on the element index and element value can be calculated:
vec3 scale = 1.0 / vec3(dim);
scale = vec3(min(scale.x, min(scale.y, scale.z)));
vec3 trans = 2 * scale * (vec3(inx) - vec3(dim-1) / 2.0);
mat4 instanceMat = mat4(
vec4(scale.x * cube_scale, 0.0, 0.0, 0.0),
vec4(0.0, scale.y * cube_scale, 0.0, 0.0),
vec4(0.0, 0.0, scale.z * cube_scale, 0.0),
vec4(trans, 1.0)
);
vec4 instance_pos = instanceMat * vec4(a_pos, 1.0);
The value can be additionally visualized by the color of the cube. For this the floating point value in the range [0.0, 1.0] is transformed to a RGB color in the HSV color range:
vec3 HUEtoRGB(in float H)
{
float R = abs(H * 6.0 - 3.0) - 1.0;
float G = 2.0 - abs(H * 6.0 - 2.0);
float B = 2.0 - abs(H * 6.0 - 4.0);
return clamp( vec3(R,G,B), 0.0, 1.0 );
}
vec3 color = HUEtoRGB(0.66 * (1-0 - value));
See also OpenGL - Python examples
Pure NumPy / PyOpenGL example program. The values of the array are changed randomly:
import numpy as np
from OpenGL.GLUT import *
from OpenGL.GL import *
from OpenGL.GL.shaders import *
class MyWindow:
__glsl_vert = """
#version 450 core
layout (location = 0) in vec3 a_pos;
layout (location = 1) in vec3 a_nv;
layout (location = 2) in vec4 a_col;
out vec3 v_pos;
out vec3 v_nv;
out vec4 v_color;
layout (binding = 1) uniform sampler3D u_array3D;
uniform mat4 u_proj;
uniform mat4 u_view;
uniform mat4 u_model;
vec3 HUEtoRGB(in float H)
{
float R = abs(H * 6.0 - 3.0) - 1.0;
float G = 2.0 - abs(H * 6.0 - 2.0);
float B = 2.0 - abs(H * 6.0 - 4.0);
return clamp( vec3(R,G,B), 0.0, 1.0 );
}
void main()
{
ivec3 dim = textureSize(u_array3D, 0);
vec3 scale = 1.0 / vec3(dim);
scale = vec3(min(scale.x, min(scale.y, scale.z)));
ivec3 inx = ivec3(0);
inx.z = gl_InstanceID / (dim.x * dim.y);
inx.y = (gl_InstanceID - inx.z * dim.x * dim.y) / dim.x;
inx.x = gl_InstanceID - inx.z * dim.x * dim.y - inx.y * dim.x;
float value = texelFetch(u_array3D, inx, 0).x;
vec3 trans = 2 * scale * (vec3(inx) - vec3(dim-1) / 2.0);
mat4 instanceMat = mat4(
vec4(scale.x * value, 0.0, 0.0, 0.0),
vec4(0.0, scale.y * value, 0.0, 0.0),
vec4(0.0, 0.0, scale.z * value, 0.0),
vec4(trans, 1.0)
);
mat4 model_view = u_view * u_model * instanceMat;
mat3 normal = transpose(inverse(mat3(model_view)));
vec4 view_pos = model_view * vec4(a_pos.xyz, 1.0);
v_pos = view_pos.xyz;
v_nv = normal * a_nv;
v_color = vec4(HUEtoRGB(0.66 * (1-0 - value)), 1.0);
gl_Position = u_proj * view_pos;
}
"""
__glsl_frag = """
#version 450 core
out vec4 frag_color;
in vec3 v_pos;
in vec3 v_nv;
in vec4 v_color;
void main()
{
vec3 N = normalize(v_nv);
vec3 V = -normalize(v_pos);
float ka = 0.1;
float kd = max(0.0, dot(N, V)) * 0.9;
frag_color = vec4(v_color.rgb * (ka + kd), v_color.a);
}
"""
def __init__(self, w, h):
self.__caption = 'OpenGL Window'
self.__vp_valid = False
self.__vp_size = [w, h]
glutInit()
glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGB | GLUT_DEPTH)
glutInitWindowSize(self.__vp_size[0], self.__vp_size[1])
self.__glut_wnd = glutCreateWindow(self.__caption)
self.__program = compileProgram(
compileShader( self.__glsl_vert, GL_VERTEX_SHADER ),
compileShader( self.__glsl_frag, GL_FRAGMENT_SHADER ),
)
self.___attrib = { a : glGetAttribLocation (self.__program, a) for a in ['a_pos', 'a_nv', 'a_col'] }
print(self.___attrib)
self.___uniform = { u : glGetUniformLocation (self.__program, u) for u in ['u_model', 'u_view', 'u_proj'] }
print(self.___uniform)
v = [[-1,-1,1], [1,-1,1], [1,1,1], [-1,1,1], [-1,-1,-1], [1,-1,-1], [1,1,-1], [-1,1,-1]]
c = [[1.0, 0.0, 0.0], [1.0, 0.5, 0.0], [1.0, 0.0, 1.0], [1.0, 1.0, 0.0], [0.0, 1.0, 0.0], [0.0, 0.0, 1.0]]
n = [[0,0,1], [1,0,0], [0,0,-1], [-1,0,0], [0,1,0], [0,-1,0]]
e = [[0,1,2,3], [1,5,6,2], [5,4,7,6], [4,0,3,7], [3,2,6,7], [1,0,4,5]]
index_array = [si*4+[0, 1, 2, 0, 2, 3][vi] for si in range(6) for vi in range(6)]
attr_array = []
for si in range(len(e)):
for vi in e[si]:
attr_array += [*v[vi], *n[si], *c[si], 1]
self.__no_vert = len(attr_array) // 10
self.__no_indices = len(index_array)
vertex_attributes = np.array(attr_array, dtype=np.float32)
indices = np.array(index_array, dtype=np.uint32)
self.__vao = glGenVertexArrays(1)
self.__vbo, self.__ibo = glGenBuffers(2)
glBindVertexArray(self.__vao)
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, self.__ibo)
glBufferData(GL_ELEMENT_ARRAY_BUFFER, indices, GL_STATIC_DRAW)
glBindBuffer(GL_ARRAY_BUFFER, self.__vbo)
glBufferData(GL_ARRAY_BUFFER, vertex_attributes, GL_STATIC_DRAW)
float_size = vertex_attributes.itemsize
glVertexAttribPointer(0, 3, GL_FLOAT, False, 10*float_size, None)
glVertexAttribPointer(1, 3, GL_FLOAT, False, 10*float_size, c_void_p(3*float_size))
glVertexAttribPointer(2, 4, GL_FLOAT, False, 10*float_size, c_void_p(6*float_size))
glEnableVertexAttribArray(0)
glEnableVertexAttribArray(1)
glEnableVertexAttribArray(2)
glEnable(GL_DEPTH_TEST)
glUseProgram(self.__program)
shape = [5, 4, 6]
number_of = shape[0] * shape[1] * shape[2]
self.array3d = np.array(np.random.rand(number_of), dtype=np.float32).reshape(shape)
glActiveTexture(GL_TEXTURE1)
self.tex3DObj = glGenTextures(1)
glBindTexture(GL_TEXTURE_3D, self.tex3DObj)
glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_MAX_LEVEL, 0)
glTexImage3D(GL_TEXTURE_3D, 0, GL_R16F, *self.array3d.shape, 0, GL_RED, GL_FLOAT, self.array3d)
glutReshapeFunc(self.__reshape)
glutDisplayFunc(self.__mainloop)
def run(self):
self.__starttime = 0
self.__starttime = self.elapsed_ms()
glutMainLoop()
def elapsed_ms(self):
return glutGet(GLUT_ELAPSED_TIME) - self.__starttime
def __reshape(self, w, h):
self.__vp_valid = False
def __mainloop(self):
number_of = self.array3d.shape[0] * self.array3d.shape[1] * self.array3d.shape[2]
rand = (np.random.rand(number_of) - 0.5) * 0.05
self.array3d = np.clip(np.add(self.array3d, rand.reshape(self.array3d.shape)), 0, 1)
glTexSubImage3D(GL_TEXTURE_3D, 0, 0, 0, 0, *self.array3d.shape, GL_RED, GL_FLOAT, self.array3d)
if not self.__vp_valid:
self.__vp_size = [glutGet(GLUT_WINDOW_WIDTH), glutGet(GLUT_WINDOW_HEIGHT)]
self.__vp_valid = True
glViewport(0, 0, self.__vp_size[0], self.__vp_size[1])
aspect, ta, near, far = self.__vp_size[0]/self.__vp_size[1], np.tan(np.radians(90.0) / 2), 0.1, 10
proj = np.array(((1/ta/aspect, 0, 0, 0), (0, 1/ta, 0, 0), (0, 0, -(far+near)/(far-near), -1), (0, 0, -2*far*near/(far-near), 0)), np.float32)
view = np.array(((1, 0, 0, 0), (0, 0, -1, 0), (0, 1, 0, 0), (0, 0, -2, 1)), np.float32)
c, s = (f(np.radians(30.0)) for f in [np.cos, np.sin])
viewRotX = np.array(((1, 0, 0, 0), (0, c, s, 0), (0, -s, c, 0), (0, 0, 0, 1)), np.float32)
view = np.matmul(viewRotX, view)
c1, s1, c2, s2, c3, s3 = (f(self.elapsed_ms() * np.pi * 2 / tf) for tf in [5000.0, 7333.0, 10000.0] for f in [np.cos, np.sin])
rotMatZ = np.array(((c3, s3, 0, 0), (-s3, c3, 0, 0), (0, 0, 1, 0), (0, 0, 0, 1)), np.float32)
model = rotMatZ
glUniformMatrix4fv(self.___uniform['u_proj'], 1, GL_FALSE, proj )
glUniformMatrix4fv(self.___uniform['u_view'], 1, GL_FALSE, view )
glUniformMatrix4fv(self.___uniform['u_model'], 1, GL_FALSE, model )
glClearColor(0.2, 0.3, 0.3, 1.0)
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)
glDrawElementsInstanced(GL_TRIANGLES, self.__no_indices, GL_UNSIGNED_INT, None, number_of)
glutSwapBuffers()
glutPostRedisplay()
window = MyWindow(800, 600)
window.run()

This won't directly create the sort of visualization you're looking for, but I would highly recommend taking a look at the glumpy package by Nicholas Rougier : https://code.google.com/p/glumpy/. OpenGL can be a pain to use, especially for someone who is not a graphics expert, and glumpy abstracts away most of the pain to let you just display numpy arrays on the screen.

Related

Why is my PyOpenGL program using shaders not displaying anything?

I have the following minimal shader example (running in Python 2.7). I expect to see the entire screen taken up by a shaded quad (that is red). Instead I just see black. This example used to work perfectly on my previous Mac machine but now it seizes to function -- am I missing some OpenGL call that is needed?
from OpenGL.GLU import *
from OpenGL.GL import *
from OpenGL.GLUT import *
from OpenGL.GLUT.freeglut import glutLeaveMainLoop
from OpenGL.arrays import vbo
from OpenGL.GL import shaders
from OpenGL.GL.ARB.color_buffer_float import *
from OpenGL.raw.GL.ARB.color_buffer_float import *
import numpy as np
QUAD_VERTEX_SHADER = """
attribute vec2 points;
varying vec2 coord;
varying vec2 index;
void main() {
index = (points + 1.0) / 2.0;
coord = (points * vec2(1, -1) + 1.0) / 2.0;
gl_Position = vec4(points, 0.0, 1.0);
}
"""
FRAG_SHADER = """
varying vec2 index;
void main() {
gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0);
}
"""
def display():
glViewport(0, 0, int(width), int(height))
shaders.glUseProgram(shader)
vbo.bind()
glEnableClientState(GL_VERTEX_ARRAY)
glVertexPointerf(vbo)
glDrawArrays(GL_TRIANGLES, 0, 6)
vbo.unbind()
glDisableClientState(GL_VERTEX_ARRAY)
glutSwapBuffers()
def idle():
glutPostRedisplay()
width = 640
height = 480
vertices = [
[ -1, 1, 0 ],
[ -1,-1, 0 ],
[ 1,-1, 0 ],
[ -1, 1, 0 ],
[ 1,-1, 0 ],
[ 1, 1, 0 ]
]
glutInit(sys.argv)
glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGBA | GLUT_ACCUM | GLUT_DEPTH)
glutInitWindowSize(width, height)
window_id = glutCreateWindow('Visualization')
glClearColor(0.,0.,0.,0.)
glClampColor(GL_CLAMP_READ_COLOR, GL_FALSE)
glEnable(GL_CULL_FACE)
glEnable(GL_DEPTH_TEST)
glEnable( GL_PROGRAM_POINT_SIZE )
glMatrixMode(GL_MODELVIEW)
glutIdleFunc(idle)
glutDisplayFunc(display)
vp = shaders.compileShader(QUAD_VERTEX_SHADER, GL_VERTEX_SHADER)
fp = shaders.compileShader(FRAG_SHADER, GL_FRAGMENT_SHADER)
shader = shaders.compileProgram(vp, fp)
vbo = vbo.VBO(np.array(vertices,'float32'))
glutMainLoop()
You have to specify the array of vertex attributes with glVertexAttribPointer and you have to enable it with glEnableVertexAttribArray:
shader = shaders.compileProgram(vp, fp)
points_loc = glGetAttribLocation(shader, 'points');
glUseProgram(shader)
vbo.bind()
glEnableVertexAttribArray(points_loc);
glVertexAttribPointer(points_loc, 3, GL_FLOAT, GL_FALSE, 0, None)
glDrawArrays(GL_TRIANGLES, 0, 6)

Moderngl: project photo with angle

I am trying to project a photo with some angle.
If the photo was taken when the camera was looking straight ahead, then the camera angles (yaw, pitch, roll) are all zero.
Now let's say that the camera was looking somewhat upwards, let's say with pitch=1 radians, then the photo is actually capturing a trapezoid and not a rectangle:
Now let's get to the code - this is a simple program that projects a photo with no angles, using moderngl-window:
import moderngl
import moderngl_window
import numpy as np
from PIL import Image
class Pygame(moderngl_window.WindowConfig):
window_size = 1280, 720
def __init__(self, **kwargs):
super().__init__(**kwargs)
self.program = self.ctx.program(
vertex_shader="""
#version 330
in vec2 vertex_xy;
in vec2 vertex_uv;
uniform mat4 model;
out vec2 fragment_uv;
void main() {
vec4 p = vec4(vertex_xy, 0.0, 1.0);
gl_Position = model * p;
fragment_uv = vertex_uv;
}
""",
fragment_shader="""
#version 330
in vec2 fragment_uv;
uniform sampler2D texture0;
out vec4 fragment_color;
void main() {
fragment_color = texture(texture0, fragment_uv);
}
"""
)
self.program['model'].write(bytes(np.eye(4, dtype=np.float32)))
self.program['texture0'].value = 0
self.vertex_array = self.init_vertex_array(self.ctx, self.program)
image = Image.open('test.jpg').transpose(Image.FLIP_TOP_BOTTOM)
self.texture = self.ctx.texture(image.size, 3, image.tobytes())
self.texture.use()
def render(self, time, frametime):
self.ctx.clear()
self.vertex_array.render()
def init_vertex_array(self, context: moderngl.Context, program: moderngl.Program) -> moderngl.VertexArray:
vertices_xy = self.get_vertices_for_quad_2d(size=(2.0, 2.0), bottom_left_corner=(-1.0, -1.0))
vertex_buffer_xy = context.buffer(vertices_xy.tobytes())
vertices_uv = self.get_vertices_for_quad_2d(size=(1.0, 1.0), bottom_left_corner=(0.0, 0.0))
vertex_buffer_uv = context.buffer(vertices_uv.tobytes())
vertex_array = context.vertex_array(program, [(vertex_buffer_xy, "2f", "vertex_xy"),
(vertex_buffer_uv, "2f", "vertex_uv")])
return vertex_array
def get_vertices_for_quad_2d(self, size=(2.0, 2.0), bottom_left_corner=(-1.0, -1.0)) -> np.array:
# A quad is composed of 2 triangles: https://en.wikipedia.org/wiki/Polygon_mesh
w, h = size
x_bl, y_bl = bottom_left_corner
vertices = np.array([x_bl, y_bl + h,
x_bl, y_bl,
x_bl + w, y_bl,
x_bl, y_bl + h,
x_bl + w, y_bl,
x_bl + w, y_bl + h], dtype=np.float32)
return vertices
if __name__ == '__main__':
moderngl_window.run_window_config(Pygame, args=('--window', 'glfw'))
When you run this program you will see this window:
Now, if we edit the render function to add an angle:
def render(self, time, frametime):
pitch_rad = 1
rotate_around_y_pitch = np.array([[np.cos(pitch_rad), 0, np.sin(pitch_rad), 0],
[0, 1, 0, 0],
[-np.sin(pitch_rad), 0, np.cos(pitch_rad), 0],
[0, 0, 0, 1]], dtype=np.float32)
self.program['model'].write(bytes(rotate_around_y_pitch))
self.ctx.clear()
self.vertex_array.render()
Then the projected photo will still be rectangular (just with a change in aspect ratio) and not a trapezoid.
What am I missing?
Many thanks to #Grimmy who supplied the missing details:
I should be using a projection matrix
I should position the object far from the camera
The complete working code:
import moderngl
import moderngl_window
import numpy as np
from PIL import Image
from pyrr import Matrix44
class Pygame(moderngl_window.WindowConfig):
window_size = 1280, 720
def __init__(self, **kwargs):
super().__init__(**kwargs)
self.program = self.ctx.program(
vertex_shader="""
#version 330
in vec2 vertex_xy;
in vec2 vertex_uv;
uniform mat4 model;
uniform mat4 projection;
out vec2 fragment_uv;
void main() {
vec4 p = vec4(vertex_xy, 0.0, 1.0);
gl_Position = projection * model * p;
fragment_uv = vertex_uv;
}
""",
fragment_shader="""
#version 330
in vec2 fragment_uv;
uniform sampler2D texture0;
out vec4 fragment_color;
void main() {
fragment_color = texture(texture0, fragment_uv);
}
"""
)
self.program['model'].write(bytes(np.eye(4, dtype=np.float32)))
self.program['texture0'].value = 0
self.vertex_array = self.init_vertex_array(self.ctx, self.program)
image = Image.open('test.jpg').transpose(Image.FLIP_TOP_BOTTOM)
self.texture = self.ctx.texture(image.size, 3, image.tobytes())
self.texture.use()
def render(self, time, frametime):
pitch_rad = -1
# Important! the -3 here positions the object far from the camera
rotate_around_x_pitch = np.array([[1, 0, 0, 0],
[0, np.cos(pitch_rad), -np.sin(pitch_rad), 0],
[0, np.sin(pitch_rad), np.cos(pitch_rad), 0],
[0, 0, -3, 1]], dtype=np.float32)
projection = Matrix44.perspective_projection(45.0, self.aspect_ratio, 0.1, 1000.0, dtype="f4")
self.program["projection"].write(projection)
self.program['model'].write(bytes(rotate_around_x_pitch))
self.ctx.clear()
self.vertex_array.render()
def init_vertex_array(self, context: moderngl.Context, program: moderngl.Program) -> moderngl.VertexArray:
vertices_xy = self.get_vertices_for_quad_2d(size=(2.0, 2.0), bottom_left_corner=(-1.0, -1.0))
vertex_buffer_xy = context.buffer(vertices_xy.tobytes())
vertices_uv = self.get_vertices_for_quad_2d(size=(1.0, 1.0), bottom_left_corner=(0.0, 0.0))
vertex_buffer_uv = context.buffer(vertices_uv.tobytes())
vertex_array = context.vertex_array(program, [(vertex_buffer_xy, "2f", "vertex_xy"),
(vertex_buffer_uv, "2f", "vertex_uv")])
return vertex_array
def get_vertices_for_quad_2d(self, size=(2.0, 2.0), bottom_left_corner=(-1.0, -1.0)) -> np.array:
# A quad is composed of 2 triangles: https://en.wikipedia.org/wiki/Polygon_mesh
w, h = size
x_bl, y_bl = bottom_left_corner
vertices = np.array([x_bl, y_bl + h,
x_bl, y_bl,
x_bl + w, y_bl,
x_bl, y_bl + h,
x_bl + w, y_bl,
x_bl + w, y_bl + h], dtype=np.float32)
return vertices
if __name__ == '__main__':
moderngl_window.run_window_config(Pygame, args=('--window', 'glfw'))

Problem making Sphere using PyOpenGL and PyQt5 [closed]

Closed. This question is not reproducible or was caused by typos. It is not currently accepting answers.
This question was caused by a typo or a problem that can no longer be reproduced. While similar questions may be on-topic here, this one was resolved in a way less likely to help future readers.
Closed 3 years ago.
Improve this question
I am trying to make sphere with OpenGL in Python but only single red dot appears in the center of screen. My code is as follow
def paintGL(self):
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)
try:
glUseProgram(self.shader)
glBindVertexArray(self.vao)
glDrawArrays(GL_POINTS, 0, int(self.buff_size/3))
finally:
glBindVertexArray(0)
glUseProgram(0)
def geometry(self):
self.vao = glGenVertexArrays(1)
self.vbo = glGenBuffers(1)
lats = 180
longs = 360
r = 0.5
data = []
for i in range(lats):
lat0 = np.pi * (-0.5 + (i - 1) / lats)
z0 = np.sin(lat0)
zr0 = np.cos(lat0)
lat1 = np.pi * (-0.5 + i / lats)
z1 = np.sin(lat1)
zr1 = np.cos(lat1)
for j in range(longs):
lng = 2 * np.pi * (j - 1) / longs
x_cord = np.cos(lng)
y = np.sin(lng)
data.extend([x_cord * zr0, y * zr0, z0])
data.extend([r * x_cord * zr0, r * y * zr0, r * z0])
data.extend([x_cord * zr1, y * zr1, z1])
data.extend([r * x_cord * zr1, r * y * zr1, r * z1])
data_input = np.array(data, dtype=np.float32)
self.buff_size = len(data_input)
vertex_size = int(data_input.nbytes / self.buff_size) * 3
glUseProgram(self.shader)
glBindVertexArray(self.vao)
glBindBuffer(GL_ARRAY_BUFFER, self.vbo)
glBufferData(GL_ARRAY_BUFFER, data_input.nbytes, data_input, GL_DYNAMIC_DRAW)
self.vertex_position = glGetAttribLocation(self.shader, 'position')
glVertexAttribPointer(self.vertex_position, 3, GL_FLOAT, GL_FALSE, vertex_size, None)
glEnableVertexAttribArray(self.vertex_position)
glDisableVertexAttribArray(self.vertex_position)
glBindBuffer(GL_ARRAY_BUFFER, 0)
glBindVertexArray(0)
glUseProgram(0)
Shader for my program is:
self.vertex_shader = shaders.compileShader(
"""
#version 330
in vec3 pos;
uniform mat4 movement;
void main()
{
gl_Position = movement * vec4(pos, 1.0);
}
"""
, GL_VERTEX_SHADER
)
self.fragment_shader = shaders.compileShader(
"""
#version 330
out vec4 gl_FragColor;
void main()
{
gl_FragColor = vec4(1.0,0.0,0.5,1.0);
}
"""
, GL_FRAGMENT_SHADER
)
self.shader = shaders.compileProgram(self.vertex_shader, self.fragment_shader)
I don't know where I'm having issue. I also tried using indices with GL_ELEMENT_ARRAY_BUFFER and glDrawElements as GL_QUAD_STRIP but no success. If you know how I can fix this then please share. Thanks
The name of the vertex attribute is pos:
in vec3 pos;
So the argument to glGetAttribLocation has to be 'pos' rather than 'position':
self.vertex_position = glGetAttribLocation(self.shader, 'position')
self.vertex_position = glGetAttribLocation(self.shader, 'pos')
If the specification and enable state is stored in the Vertex Array Object. If you glDisableVertexAttribArray(self.vertex_position), this state is stored to the self.vao. Remove it:
glBindVertexArray(self.vao)
glBindBuffer(GL_ARRAY_BUFFER, self.vbo)
glBufferData(GL_ARRAY_BUFFER, data_input.nbytes, data_input, GL_DYNAMIC_DRAW)
self.vertex_position = glGetAttribLocation(self.shader, 'pos')
glVertexAttribPointer(self.vertex_position, 3, GL_FLOAT, GL_FALSE, vertex_size, None)
glEnableVertexAttribArray(self.vertex_position)
#glDisableVertexAttribArray(self.vertex_position) # <--- DELETE
glBindBuffer(GL_ARRAY_BUFFER, 0)
glBindVertexArray(0)
A matrix unfiorm be default is initialized with all fields 0.
uniform mat4 movement;
If you do not need the uniform, then remove it. Or initialize it by the Identity matrix.
glUseProgram(self.shader)
ident4x4 = np.identity(4, np.float32)
self.movement_loc = glGetUniformLocation(self.shader, 'movement')
glUniformMatrix4fv(self.movement_loc, 1, GL_FALSE, ident4x4)
Alternatively you can use a view and perspective projection matrix (self.width and self.height are the width and height of the window):
# projection matrix
aspect, ta, near, far = self.width/self.height, np.tan(np.radians(90.0) / 2), 0.1, 10
proj = np.matrix(((1/ta/aspect, 0, 0, 0), (0, 1/ta, 0, 0), (0, 0, -(far+near)/(far-near), -1), (0, 0, -2*far*near/(far-near), 0)), np.float32)
# view matrix
view = np.matrix(((1, 0, 0, 0), (0, 1, 0, 0), (0, 0, 1, 0), (0, 0, -3, 1)), np.float32)
glUniformMatrix4fv(self.movement_loc, 1, GL_FALSE, view * proj)

Which are the modern glTranslate and glRotate alternatives? [closed]

Closed. This question needs to be more focused. It is not currently accepting answers.
Want to improve this question? Update the question so it focuses on one problem only by editing this post.
Closed 3 years ago.
Improve this question
Important edit: I am using the PyOpenGL binding of OpenGL
I am trying not to use the glRotate and glTranslate functions, but I have not found any alternative to this two functions. This functions are deprecated. What can I use?
The modern way is to write a Shader program, to use Vertex Array Objects and to use Uniform variable(s) of type mat4.
There is more code to write, in compare to the deprecated fixed function pipeline but the benefit is high flexibility and a far better performance, in compare to drawing by glBegin/glEnd sequences.
For the matrix calculations can be used the PyGLM library, which is the python version of the c++ OpenGL Mathematics (glm) library.
e.g. A scale, rotation matrix around the z-axis by an angle and translation can set by:
model = glm.mat4(1)
model = glm.translate(model, glm.vec3(0.2, 0.2, 0))
model = glm.rotate(model, angle, glm.vec3(0, 0, 1))
model = glm.scale(model, glm.vec3(0.5, 0.5, 1))
In compare to the the Legacy OpenGL operation glRoatate, the angle has to be set in radians.
Note, instead of using PyGLM it is also possible to use the popular NumPy library and numpy.matrix, but PyGLM is closer to that what you know from Legacy OpenGL and the functions glScale, glTranslate and glRotate.
Of course it would be possible to set the the 4x4 matrices with out any library and to implement the matrix operations by yourself, too.
See the small example program, which uses PyOpenGL and PyGLM (beside the modules math and ctypes):
import math
import ctypes
import glm
from OpenGL.GLUT import *
from OpenGL.GL import *
from OpenGL.GL.shaders import *
class MyWindow:
__caption = 'OpenGL Window'
__vp_size = [800, 600]
__vp_valid = False
__glut_wnd = None
__glsl_vert = """
#version 450 core
layout (location = 0) in vec3 a_pos;
layout (location = 1) in vec4 a_col;
out vec4 v_color;
layout (location = 0) uniform mat4 u_proj;
layout (location = 1) uniform mat4 u_view;
layout (location = 2) uniform mat4 u_model;
void main()
{
v_color = a_col;
gl_Position = u_proj * u_view * u_model * vec4(a_pos.xyz, 1.0);
}
"""
__glsl_frag = """
#version 450 core
out vec4 frag_color;
in vec4 v_color;
void main()
{
frag_color = v_color;
}
"""
__program = None
__vao = None
__vbo = None
def __init__(self, w, h):
self.__vp_size = [w, h]
glutInit()
glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGB | GLUT_DEPTH)
glutInitWindowSize(self.__vp_size[0], self.__vp_size[1])
__glut_wnd = glutCreateWindow(self.__caption)
self.__program = compileProgram(
compileShader( self.__glsl_vert, GL_VERTEX_SHADER ),
compileShader( self.__glsl_frag, GL_FRAGMENT_SHADER ),
)
attribures = [
# x y z R G B A
-0.866, -0.5, 0, 1, 0, 0, 1,
0.866, -0.5, 0, 1, 1, 0, 1,
0, 1.0, 0, 0, 0, 1, 1
]
vertex_attributes = (GLfloat * len(attribures))(*attribures)
itemsize = ctypes.sizeof(ctypes.c_float)
self.__vbo = glGenBuffers(1)
glBindBuffer(GL_ARRAY_BUFFER, self.__vbo)
glBufferData(GL_ARRAY_BUFFER, vertex_attributes, GL_STATIC_DRAW)
self.__vao = glGenVertexArrays(1)
glBindVertexArray(self.__vao)
glVertexAttribPointer(0, 3, GL_FLOAT, False, 7*itemsize, None)
glEnableVertexAttribArray(0)
glVertexAttribPointer(1, 4, GL_FLOAT, False, 7*itemsize, ctypes.c_void_p(3*itemsize))
glEnableVertexAttribArray(1)
glUseProgram(self.__program)
glutReshapeFunc(self.__reshape)
glutDisplayFunc(self.__mainloop)
def run(self):
self.__starttime = 0
self.__starttime = self.elapsed_ms()
glutMainLoop()
def elapsed_ms(self):
return glutGet(GLUT_ELAPSED_TIME) - self.__starttime
def __reshape(self, w, h):
self.__vp_valid = False
def __mainloop(self):
if not self.__vp_valid:
self.__vp_size = [glutGet(GLUT_WINDOW_WIDTH), glutGet(GLUT_WINDOW_HEIGHT)]
self.__vp_valid = True
glViewport(0, 0, self.__vp_size[0], self.__vp_size[1])
proj = glm.mat4(1)
view = glm.mat4(1)
model = glm.mat4(1)
aspect = self.__vp_size[0]/self.__vp_size[1]
aspect_x = aspect if self.__vp_size[0] > self.__vp_size[1] else 1.0
aspect_y = 1/aspect if self.__vp_size[0] < self.__vp_size[1] else 1.0
proj = glm.ortho(-aspect_x, aspect_x, -aspect_y, aspect_y, -1.0, 1.0)
angle = self.elapsed_ms() * math.pi * 2 / 3000.0
model = glm.translate(model, glm.vec3(0.2, 0.2, 0))
model = glm.rotate(model, angle, glm.vec3(0, 0, 1))
model = glm.scale(model, glm.vec3(0.5, 0.5, 1))
glUniformMatrix4fv(0, 1, GL_FALSE, glm.value_ptr(proj) )
glUniformMatrix4fv(1, 1, GL_FALSE, glm.value_ptr(view) )
glUniformMatrix4fv(2, 1, GL_FALSE, glm.value_ptr(model) )
glClearColor(0.2, 0.3, 0.3, 1.0)
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)
glDrawArrays(GL_TRIANGLES, 0, 3)
glutSwapBuffers()
glutPostRedisplay()
window = MyWindow(800, 600)
window.run()

How to get clip distances and textures on green dragon rendering

The source is of a green dragon rendering. My question is how to get the clip distances working on it? Also, the textures are not appearing as the expected output. Any ideas what can be modified in the source code to render the program as expected?
Update: With the excellent help and superb answers of Rabbid76 the clip distance is working and texture loading is working! Thank You.
Bonus example: clipdistance_torus_package.zip a clip distance example with a torus and textures!
Expected output:
Files to run: clipdistance_dragon.zip
#!/usr/bin/python3
import sys
import time
import ctypes
fullscreen = True
sys.path.append("./shared")
from sbmloader import SBMObject # location of sbm file format loader
from ktxloader import KTXObject
from sbmath import m3dDegToRad, m3dRadToDeg, m3dTranslateMatrix44, m3dRotationMatrix44, m3dMultiply, m3dOrtho, m3dPerspective, rotation_matrix, translate, m3dScaleMatrix44, \
scale, m3dLookAt, normalize
try:
from OpenGL.GLUT import *
from OpenGL.GL import *
from OpenGL.GLU import *
from OpenGL.raw.GL.ARB.vertex_array_object import glGenVertexArrays, glBindVertexArray
except:
print ('''
ERROR: PyOpenGL not installed properly.
''')
sys.exit()
import numpy as np
from math import cos, sin
identityMatrix = [1,0,0,0, 0,1,0,0, 0,0,1,0, 0,0,0,1]
myobject = SBMObject()
render_program = GLuint(0)
paused = False
class uniforms:
proj_matrix = GLint(0)
mv_matrix = GLint(0)
clip_plane = GLint(0)
clip_sphere = GLint(0)
uniform = uniforms()
def shader_load(filename, shader_type):
result = GLuint(0)
with open ( filename, "rb") as data:
result = glCreateShader(shader_type)
glShaderSource(result, data.read() )
glCompileShader(result)
if not glGetShaderiv(result, GL_COMPILE_STATUS):
print( 'compile error:' )
print( glGetShaderInfoLog(result) )
return result
def link_from_shaders(shaders, shader_count, delete_shaders, check_errors=False):
program = GLuint(0)
program = glCreateProgram()
for i in range(0, shader_count):
glAttachShader(program, shaders[i])
glLinkProgram(program)
if not glGetProgramiv(program, GL_LINK_STATUS):
print( 'link error:' )
print( glGetProgramInfoLog(program) )
if (delete_shaders):
for i in range(0, shader_count):
glDeleteShader(shaders[i])
return program
def load_shaders():
global render_program
global uniform
if (render_program):
glDeleteProgram(render_program);
shaders = [
shader_load("render.vs.glsl", GL_VERTEX_SHADER),
shader_load("render.fs.glsl", GL_FRAGMENT_SHADER)
]
render_program = link_from_shaders(shaders, 2, True)
uniform.proj_matrix = glGetUniformLocation(render_program, "proj_matrix");
uniform.mv_matrix = glGetUniformLocation(render_program, "mv_matrix");
uniform.clip_plane = glGetUniformLocation(render_program, "clip_plane");
uniform.clip_sphere = glGetUniformLocation(render_program, "clip_sphere");
tex_dragon=None
class Scene:
def __init__(self, width, height):
global myobject, tex_dragon
myobject.load("dragon.sbm");
load_shaders()
ktxobj = KTXObject()
tex_dragon = ktxobj.ktx_load("pattern1.ktx")
def display(self):
global paused
currentTime = time.time()
black = [ 0.0, 0.0, 0.0, 0.0 ]
one = 1.0
last_time = 0.0
total_time = 0.0
if (not paused):
total_time += (currentTime - last_time)
last_time = currentTime
f = total_time
glClearBufferfv(GL_COLOR, 0, black)
glClearBufferfv(GL_DEPTH, 0, one)
glUseProgram(render_program)
proj_matrix = (GLfloat * 16)(*identityMatrix)
proj_matrix = m3dPerspective(m3dDegToRad(50.0), float(self.width) / float(self.height), 0.1, 1000.0)
T1 = (GLfloat * 16)(*identityMatrix)
m3dTranslateMatrix44(T1, 0.0, 0.0, -15.0)
RY = (GLfloat * 16)(*identityMatrix)
m3dRotationMatrix44(RY, f * 0.34, 0.0, 1.0, 0.0)
T2 = (GLfloat * 16)(*identityMatrix)
m3dTranslateMatrix44(T2, 0.0, -4.0, 0.0)
mv_matrix = (GLfloat * 16)(*identityMatrix)
mv_matrix = m3dMultiply(T1, m3dMultiply(RY, T2))
RX = (GLfloat * 16)(*identityMatrix)
m3dRotationMatrix44(RX, f * 6.0, 1.0, 0.0, 0.0)
RY = (GLfloat * 16)(*identityMatrix)
m3dRotationMatrix44(RY, f * 7.3, 0.0, 1.0, 0.0)
plane_matrix = (GLfloat * 16)(*identityMatrix)
plane_matrix = m3dMultiply(RX , RY )
plane = plane_matrix[0:4]
plane[3] = 0
plane = normalize(plane)
clip_sphere = [sin(f * 0.7) * 3.0, cos(f * 1.9) * 3.0, sin(f * 0.1) * 3.0, cos(f * 1.7) + 2.5]
glUniformMatrix4fv(uniform.proj_matrix, 1, GL_FALSE, proj_matrix)
glUniformMatrix4fv(uniform.mv_matrix, 1, GL_FALSE, mv_matrix)
glUniform4fv(uniform.clip_plane, 1, plane)
glUniform4fv(uniform.clip_sphere, 1, clip_sphere)
glEnable(GL_DEPTH_TEST)
glEnable(GL_CLIP_DISTANCE0)
glEnable(GL_CLIP_DISTANCE1)
glActiveTexture(GL_TEXTURE0)
glBindTexture(GL_TEXTURE_2D, tex_dragon)
myobject.render()
glutSwapBuffers()
def reshape(self, width, height):
self.width = width
self.height = height
def keyboard(self, key, x, y ):
global fullscreen
global paused
print ('key:' , key)
if key == b'\x1b': # ESC
sys.exit()
elif key == b'f' or key == b'F': #fullscreen toggle
if (fullscreen == True):
glutReshapeWindow(512, 512)
glutPositionWindow(int((1360/2)-(512/2)), int((768/2)-(512/2)))
fullscreen = False
else:
glutFullScreen()
fullscreen = True
elif key == b'p' or key == b'P':
paused = not paused
elif key == b'r' or key == b'R':
pass
print('done')
def init(self):
pass
def timer(self, blah):
glutPostRedisplay()
glutTimerFunc( int(1/60), self.timer, 0)
time.sleep(1/60.0)
if __name__ == '__main__':
start = time.time()
glutInit()
glutInitDisplayMode(GLUT_RGBA | GLUT_DOUBLE | GLUT_DEPTH)
glutInitWindowSize(512, 512)
w1 = glutCreateWindow('OpenGL SuperBible - Clip Distance')
glutInitWindowPosition(int((1360/2)-(512/2)), int((768/2)-(512/2)))
fullscreen = False
#glutFullScreen()
scene = Scene(512,512)
glutReshapeFunc(scene.reshape)
glutDisplayFunc(scene.display)
glutKeyboardFunc(scene.keyboard)
glutIdleFunc(scene.display)
#glutTimerFunc( int(1/60), scene.timer, 0)
scene.init()
glutMainLoop()
ported into python from clipdistance.cpp , source of sbmobject.cpp just in case sbmloader.py is in question for the texture issue.
The C++ code from the example
vmath::mat4 plane_matrix = vmath::rotate(f * 6.0f, 1.0f, 0.0f, 0.0f) *
vmath::rotate(f * 7.3f, 0.0f, 1.0f, 0.0f);
corresponds to the following python code
RX = (GLfloat * 16)(*identityMatrix)
m3dRotationMatrix44(RX, f * 6.0, 1.0, 0.0, 0.0)
RY = (GLfloat * 16)(*identityMatrix)
m3dRotationMatrix44(RY, f * 7.3, 0.0, 1.0, 0.0)
plane_matrix = (GLfloat * 16)(*identityMatrix)
plane_matrix = m3dMultiply(RX , RY)
Note, you've to swap RX and RY in the matrix multiplication.
Your functions length and normalize can only deal with vectors which have 3 components (x, y, z). In compare the C++ function vmath::normalize from the example can handle vectors with 4 components (x, y, z, w), too.
Further the "division by zero" handling is missing in normalize.
Adapt the functions normalize and length, to deal with any vector size.
If the length of a vector is 0, then all its components are 0. There is no correct solution for that, so just return a copy of the vector itself.
def length(v):
sum_sq = sum([s*s for s in v])
return math.sqrt(sum_sq)
def normalize(v):
l = length(v)
if l == 0.0:
return v[:]
return [s/l for s in v]
Now you can port the C++ code from the example
vmath::vec4 plane = plane_matrix[0];
plane[3] = 0.0f;
plane = vmath::normalize(plane);
very straight to python:
plane = plane_matrix[0:4]
plane[3] = 0
plane = normalize(plane)
Further, there is an issue in the sbmloader module.
Only the array of vertex coordinates and texture coordinates is specified. The normal vectors are skipped.
Just skip the line
if attrib.name=='position' or attrib.name=='map1':
to fix the issue:
for attrib_i, attrib in enumerate(vertex_attrib_chunk.attrib_data):
#if attrib.name=='position' or attrib.name=='map1':
glVertexAttribPointer(attrib_i,
attrib.size, attrib.type,
GL_TRUE if (attrib.flags & SB6M_VERTEX_ATTRIB_FLAG_NORMALIZED) != 0 else GL_FALSE,
attrib.stride, ctypes.c_void_p(int(attrib.data_offset)))
glEnableVertexAttribArray(attrib_i)
If you additionally want to wrap a texture to the model, then you've to add the texture coordinate attribute to the vertex shader:
layout (location = 2) in vec2 tc;
And to pass it by an output to the next shader stage
out VS_OUT
{
vec3 N;
vec3 L;
vec3 V;
vec2 T;
} vs_out;
void main()
{
// ...
vs_out.T = tc;
// ...
}
In the fragment shader you've to add the texture sampler uniform
layout (binding = 0) uniform sampler2D tex;
Read the color form the texture
vec4 texColor = texture(tex, fs_in.T);
Multiply the output color by the texture color
color = vec4(diffuse + specular + rim, 1.0) * texColor;
Fragment shader (note, I changed diffuse_albedo):
#version 420 core
// Output
layout (location = 0) out vec4 color;
// Input from vertex shader
in VS_OUT
{
vec3 N;
vec3 L;
vec3 V;
vec2 T;
} fs_in;
// Material properties
uniform vec3 diffuse_albedo = vec3(0.5);
uniform vec3 specular_albedo = vec3(0.7);
uniform float specular_power = 128.0;
uniform vec3 rim_color = vec3(0.1, 0.2, 0.2);
uniform float rim_power = 5.0;
layout (binding = 0) uniform sampler2D tex;
vec3 calculate_rim(vec3 N, vec3 V)
{
float f = 1.0 - dot(N, V);
f = smoothstep(0.0, 1.0, f);
f = pow(f, rim_power);
return f * rim_color;
}
void main(void)
{
// Normalize the incoming N, L and V vectors
vec3 N = normalize(fs_in.N);
vec3 L = normalize(fs_in.L);
vec3 V = normalize(fs_in.V);
// Calculate R locally
vec3 R = reflect(-L, N);
// Compute the diffuse and specular components for each fragment
vec3 diffuse = max(dot(N, L), 0.0) * diffuse_albedo;
vec3 specular = pow(max(dot(R, V), 0.0), specular_power) * specular_albedo;
vec3 rim = calculate_rim(N, V);
// read color from the texture
vec4 texColor = texture(tex, fs_in.T);
// Write final color to the framebuffer
color = vec4(diffuse + specular + rim, 1.0) * texColor;
}
I recommend to add shader compile and link error logging:
glCompileShader(result)
if not glGetShaderiv(result, GL_COMPILE_STATUS):
print( 'compile error:' )
print( glGetShaderInfoLog(result) )
glLinkProgram(program)
if not glGetProgramiv(program, GL_LINK_STATUS):
print( 'link error:' )
print( glGetProgramInfoLog(program) )
Read a texture at the initialization of the application
class Scene:
def __init__(self, width, height):
global myobject, tex_dragon
myobject.load("dragon.sbm")
load_shaders()
ktxobj = KTXObject()
tex_dragon = ktxobj.ktx_load("texture_file_name.ktx")
Bind the texture before drawing the model
glActiveTexture(GL_TEXTURE0)
glBindTexture(GL_TEXTURE_2D, tex_dragon)
myobject.render()

Categories