Rendered model disappears at some asymptote - python

I've been trying to render a simple teapot with PyOpenGL, but have been running into strange issues. I can't seem to figure out exactly where the error originates from, despite the simplicity of the code.
Main.py
import pygame
from pygame.locals import *
from MV import *
import ctypes
from OpenGL.GL import *
from OpenGL.GL import shaders
from OpenGL.GLU import *
import teapot as tp
vertex_shader = '''
#version 420
in vec3 vpos_modelspace;
in vec3 vnorm_modelspace;
uniform mat4 mvp;
out vec4 vertcolor;
void main(){
vertcolor = vec4(vnorm_modelspace, 1.0);
gl_Position = mvp * vec4(vpos_modelspace, 1.0);
}
'''
fragment_shader = '''
#version 420
in vec4 vertcolor;
out vec4 fragcolor;
void main(){
fragcolor = vertcolor;
}
'''
model = tp.teapot
pygame.init()
canvas = pygame.display.set_mode((800, 600), DOUBLEBUF|OPENGL)
pygame.display.set_caption('Test')
glClearColor(.5, .5, .5, 1)
glEnable(GL_DEPTH_TEST)
glDepthFunc(GL_LESS)
glDisable(GL_CULL_FACE)
VERTEXSHADER = shaders.compileShader(vertex_shader, GL_VERTEX_SHADER)
FRAGMENTSHADER = shaders.compileShader(fragment_shader, GL_FRAGMENT_SHADER)
program = shaders.compileProgram(VERTEXSHADER, FRAGMENTSHADER)
glUseProgram(program)
vpos_loc = glGetAttribLocation(program, 'vpos_modelspace')
vnorm_loc = glGetAttribLocation(program, 'vnorm_modelspace')
mvp_loc = glGetUniformLocation(program, 'mvp')
eye = numpy.array([0, 0, 1], dtype=numpy.float32)
at = numpy.array([0, 0, 0], dtype=numpy.float32)
up = numpy.array([0, 1, 0], dtype=numpy.float32)
mvp = frustum(-1, 1, 1, -1, .1, 1000)#lookAt(eye, at, up)
vao = glGenVertexArrays(1)
glBindVertexArray(vao)
vbo_pos = glGenBuffers(1)
glBindBuffer(GL_ARRAY_BUFFER, vbo_pos)
vbo_norm = glGenBuffers(1)
glBindBuffer(GL_ARRAY_BUFFER, vbo_norm)
verts = []
normals = []
for i in range(0, len(model.faces), 3):
index = model.faces[i:i+3]
verts.extend(model.vertices[3*index[0]:3*index[0]+3])
verts.extend(model.vertices[3*index[1]:3*index[1]+3])
verts.extend(model.vertices[3*index[2]:3*index[2]+3])
normals.extend(model.normals[3*index[0]:3*index[0]+3])
normals.extend(model.normals[3*index[1]:3*index[1]+3])
normals.extend(model.normals[3*index[2]:3*index[2]+3])
verts = numpy.array(verts, dtype=numpy.float32)
normals = numpy.array(normals, dtype=numpy.float32)
glBindBuffer(GL_ARRAY_BUFFER, vbo_pos)
glBufferData(GL_ARRAY_BUFFER, verts.size * verts.itemsize, verts, GL_STATIC_DRAW)
glVertexAttribPointer(vpos_loc, 3, GL_FLOAT, GL_FALSE, 0, ctypes.c_void_p(0))
glEnableVertexAttribArray(vpos_loc)
glBindBuffer(GL_ARRAY_BUFFER, vbo_norm)
glBufferData(GL_ARRAY_BUFFER, normals.size * normals.itemsize, normals, GL_STATIC_DRAW)
glVertexAttribPointer(vnorm_loc, 3, GL_FLOAT, GL_FALSE, 0, ctypes.c_void_p(0))
glEnableVertexAttribArray(vnorm_loc)
glBindBuffer(GL_ARRAY_BUFFER, 0)
glBindVertexArray(0)
while(True):
for event in pygame.event.get():
if event.type == pygame.QUIT:
pygame.quit()
quit()
glUseProgram(program)
rotation_matrix = rotate(.01, [0, 1, 0])
mvp = mvp # rotation_matrix
glUniformMatrix4fv(mvp_loc, 1, GL_FALSE, mvp.flatten())
glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT)
glBindVertexArray(vao)
glDrawArrays(GL_TRIANGLES, 0, int(verts.size/3))
glBindVertexArray(0)
glUseProgram(0)
pygame.display.flip()
main()
MV.py
import numpy
def normalize(vector):
return vector/numpy.linalg.norm(vector)
def translate(pos):
return numpy.array([[1, 0, 0, pos[0]],
[0, 1, 0, pos[1]],
[0, 0, 1, pos[2]],
[0, 0, 0, 1]], dtype=numpy.float32)
def rotate(angle, axis):
rads = angle * numpy.pi/180
v = normalize(axis)
c = numpy.cos(rads)
omc = 1-c
s = numpy.sin(rads)
return numpy.array([[v[0]*v[0]*omc + c, v[0]*v[1]*omc - v[2]*s, v[0]*v[2]*omc + v[1]*s, 0],
[v[0]*v[1]*omc + v[2]*s, v[1]*v[1]*omc + c, v[1]*v[2]*omc - v[0]*s, 0],
[v[0]*v[2]*omc - v[1]*s, v[1]*v[2]*omc + v[0]*s, v[2]*v[2]*omc + c, 0],
[0, 0, 0, 1]], dtype=numpy.float32)
def lookAt(eye, at, up):
n = normalize(at-eye)
u = normalize(numpy.cross(n, up))
v = normalize(numpy.cross(u, n))
rotate = numpy.array([[u[0], v[0], -n[0], 0],
[u[1], v[1], -n[1], 0],
[u[2], v[2], -n[2], 0],
[0, 0, 0, 1]], dtype=numpy.float32).transpose()
return rotate#translate(-eye)
def frustum(left, right, top, bottom, near, far):
rl = right-left
tb = top-bottom
fn = far-near
return numpy.array([[2*near/rl, 0, (right+left)/rl, 0],
[0, 2*near/tb, (top+bottom)/tb, 0],
[0, 0, -(far+near)/fn, -(2*far*near)/fn],
[0, 0, -1, 0]], dtype=numpy.float32)
The output shows the teapot being rotated (though not about the axis that I expected) and sort of shrinking and disappearing at rotations of 0, pi, 2pi, etc. I believe the teapot vertices are being processed correctly, as it does show up when rotated and is correctly shaded with normal values.
Output at 5 degrees - Model is 'growing'
Output at 30 degrees - Strange culling?
Output at 60 degrees - Relatively normal
Output at 170 degrees - Model is 'shrinking'
Output at 190 degrees - Model is 'growing' on the other side of the plane
At rotations 0, pi, 2pi, etc the model is completely invisible/too small to see.

Related

Rendering a 2D quad from a VBO with PyOpenGL?

How Can You Render A Quad From A VBO using PyOpenGL and Pygame?
I am trying to get an image to render on my screen using PyOoenGL and pygame, while not using any VBO's I managed to get it to display using this draw function:
def draw(texture: Texture):
texture.Bind()
glBegin(GL_QUADS)
glTexCoord2f(0, 0)
glVertex2f(-1, -1)
glTexCoord2f(1, 0)
glVertex2f(1, -1)
glTexCoord2f(1, 1)
glVertex2f(1, 1)
glTexCoord2f(0, 1)
glVertex2f(-1, 1)
glEnd()
However, when I tried to change it out to render using a VBO I could not see well... anything:
def GenVBO():
# gen VBO for a quad # glBufferSubData(GL_ARRAY_BUFFER, offset, len(dataArray)*4, dataArray)
VBO = glGenBuffers(1)
glBindBuffer(GL_ARRAY_BUFFER, VBO)
glBufferData(GL_ARRAY_BUFFER, 64, None, GL_STATIC_DRAW)
glBufferSubData(GL_ARRAY_BUFFER, 0, 32, (GLfloat * 12)(-1, -1, 0, 0, 0, 0, 1, -1, 0, 1, 0, 0))
glBindBuffer(GL_ARRAY_BUFFER, 0)
return VBO
def draw(texture: Texture, VBO):
texture.Bind()
glBindBuffer(GL_ARRAY_BUFFER, VBO)
glEnableClientState(GL_VERTEX_ARRAY)
glEnableClientState(GL_TEXTURE_COORD_ARRAY)
glVertexPointer(2, GL_FLOAT, 32, None)
glTexCoordPointer(2, GL_FLOAT, 32, ctypes.c_void_p(8))
glDrawArrays(GL_QUADS, 0, 4)
I do not get any errors, just a blank screen, I have also tried to move around by changing my perspective with a movement script, but still no output.
And just in case it's relevant, here is the main script:
def main():
pygame.init()
display = (800, 600)
pygame.display.set_mode(display, DOUBLEBUF|OPENGL)
glEnable(GL_TEXTURE_2D)
glEnable(GL_BLEND)
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)
gluPerspective(45, (display[0]/display[1]), 0.1, 50.0)
glTranslatef(0.0, 0.0, -5)
texture = LoadImage("test.png")
VBO = GenVBO()
while True:
down = pygame.key.get_pressed()
for event in pygame.event.get():
if event.type == pygame.QUIT:
pygame.quit()
quit()
# (the movement script is here)
glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT)
draw(texture, VBO)
pygame.display.flip()
pygame.time.wait(10)
You have 2 possibiliets:
(x0, y0, u0, v0, x1, y1, u1, v1, ...)
In this case stride is 16 bytes, because each attribute tuple has 4*4 bytes (x, y, u, v). The offset of the vertices is 0 and the offset of the texture coordinates is 8 bytes:
bufferData = (GLfloat * 16)(-1, -1, 0, 0, 1, -1, 1, 0, 1, 1, 1, 1, -1, 1, 0, 1)
glBufferData(GL_ARRAY_BUFFER, bufferData, GL_STATIC_DRAW)
glVertexPointer(2, GL_FLOAT, 16, None)
glTexCoordPointer(2, GL_FLOAT, 16, ctypes.c_void_p(8))
(x0, y0, x1, y1, ..., u0, v0, u1, v1, ...)
In this case stride vor the vertices is 8 bytes and the stride for the texture coordinates is 8 bytes. The offset of the vertices is 0 and the offset of the texture coordinates is 32 bytes (8*4):
vertexData = (GLfloat * 8)(-1, -1, 1, -1, 1, 1, -1, 1)
textureData = (GLfloat * 8)( 0, 0, 1, 0, 1, 1, 0, 1)
glBufferData(GL_ARRAY_BUFFER, 64, None, GL_STATIC_DRAW)
glBufferSubData(GL_ARRAY_BUFFER, 0, 32, vertexData)
glBufferSubData(GL_ARRAY_BUFFER, 32, 32, textureData)
glVertexPointer(2, GL_FLOAT, 8, None)
glTexCoordPointer(2, GL_FLOAT, 8, ctypes.c_void_p(32))

How am I going wrong in glMultiDrawArraysIndirect function?

I'm programming using Python and Modern OpenGL, and I tried to implement the glMultiDrawArraysIndirect function in my code to draw a simple shape, I want to apply it later on to a more complex thing, but this is just a simple test that I don't know exactly where the error is.
import glfw, time, ctypes, math, pyrr
import numpy as np
from OpenGL.GL import *
from OpenGL.GL.shaders import *
glfw.init()
glfw.window_hint(glfw.SAMPLES, 4)
w = glfw.create_window(640, 480, "Galeria das Sombras", None, None)
glfw.make_context_current(w)
v = """
#version 430
in layout(location=0) vec3 posicao;
in layout(location=1) vec2 textura;
uniform mat4 view;
uniform vec3 def;
uniform vec3 pos;
uniform vec3 scale;
uniform float giro;
uniform float giro2;
out vec2 texcords;
void main(){
texcords = textura;
vec3 p = vec3(posicao.x*scale.x,posicao.y*scale.y,posicao.z*scale.z);
p = p+def;
p = vec3(-sin(giro)*p.z+cos(giro)*p.x,p.y,sin(giro)*p.x+cos(giro)*p.z);
p = vec3(p.x,-sin(giro2)*p.z+cos(giro2)*p.y,sin(giro2)*p.y+cos(giro2)*p.z);
p = p+pos+vec3(gl_InstanceID,0,0);
gl_Position = view*vec4(p,1);
}
"""
f = """
#version 430
in vec2 texcords;
uniform vec3 cor;
uniform sampler2D texinfo;
void main(){
gl_FragColor = vec4(cor,1)*texture(texinfo,texcords);
}
"""
shader = compileProgram(compileShader(v,GL_VERTEX_SHADER),compileShader(f,GL_FRAGMENT_SHADER))
tudo = [-1,-1,0,0,1,
1,-1,0,1,1,
1,1,0,1,0,
-1,1,0,0,0]
tudo = np.array(tudo, np.float32)
VBO = glGenBuffers(1)
glBindBuffer(GL_ARRAY_BUFFER, VBO)
glBufferData(GL_ARRAY_BUFFER, len(tudo)*4, tudo, GL_STATIC_DRAW)
'''
tudo = [[1,1,0,3],[2,2,1,3]]
tudo = np.array(tudo, np.uint8)
VBI = glGenBuffers(1)
glBindBuffer(GL_DRAW_INDIRECT_BUFFER, VBI)
glBufferData(GL_DRAW_INDIRECT_BUFFER, len(tudo)*4, tudo, GL_STATIC_DRAW)
'''
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 20, ctypes.c_void_p(0))
glEnableVertexAttribArray(0)
glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 20, ctypes.c_void_p(12))
glEnableVertexAttribArray(1)
glUseProgram(shader)
view = pyrr.matrix44.create_perspective_projection_matrix(60, 640/480, .1, 1000)
p = glGetUniformLocation(shader, "view")
glUniformMatrix4fv(p, 1, GL_FALSE, view)
glEnable(GL_DEPTH_TEST)
glEnable(GL_MULTISAMPLE)
glEnable(GL_TEXTURE_2D)
from PIL import Image
t = glGenTextures(1)
glBindTexture(GL_TEXTURE_2D, t)
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT)
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT)
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST)
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST)
buffer = Image.open("p.jpg")
data = buffer.tobytes()
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, buffer.size[0], buffer.size[1], 0, GL_RGB, GL_UNSIGNED_BYTE, data)
girar = 0
tempo = glfw.get_time()
tfps = glfw.get_time()
fps = 0
action = 0
while not glfw.window_should_close(w):
glfw.swap_buffers(w)
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)
view = pyrr.matrix44.create_perspective_projection_matrix(60, glfw.get_window_size(w)[0]/glfw.get_window_size(w)[1], .1, 1000)
p = glGetUniformLocation(shader, "view")
glUniformMatrix4fv(p, 1, GL_FALSE, view)
glViewport(0,0,glfw.get_window_size(w)[0],glfw.get_window_size(w)[1])
p = glGetUniformLocation(shader, "cor")
glUniform3f(p, 1, 0, 0)
p = glGetUniformLocation(shader, "def")
glUniform3f(p, 0, 0, 0)
p = glGetUniformLocation(shader, "scale")
glUniform3f(p, 1, 1, 1)
p = glGetUniformLocation(shader, "giro")
glUniform1f(p, girar*math.pi/180)
if glfw.get_time() - tempo > 1/60:
girar+=1
tempo = glfw.get_time()
if action > 0:
action-=.05
if action < 0:
action = 0
p = glGetUniformLocation(shader, "giro2")
if glfw.get_key(w, glfw.KEY_W) and action == 0:
action = 2
if action > 1:
glUniform1f(p, (1-(action-1))*-90*(math.pi/180))
else:
glUniform1f(p, action*-90*(math.pi/180))
p = glGetUniformLocation(shader, "pos")
glUniform3f(p, 0, 0, -10)
glMultiDrawArraysIndirect(GL_TRIANGLES, np.array([[0,3,1,0],[1,3,1,1]]), 2, 1)
fps+=1
if glfw.get_time() - tfps > 1:
print("FPS:",fps)
fps = 0
tfps = glfw.get_time()
glfw.poll_events()
if fps > 400:
time.sleep(.01)
glfw.destroy_window(w)
glfw.terminate()
In the VBO there is a square, but I was going to draw only 2 triangles using the first 3 points and then the last 3 next to each other, I didn't find many examples of this type of code on the internet, only glMultiDrawArraysIndirect documentation but I couldn't do it run in my code, at least not without giving a good lock, when I change the drawcount for 1 wheel but nothing appears on the screen.
There is and the drawcount I took from the site: http://docs.gl/gl4/glMultiDrawArraysIndirect
I tried to change the indirect value for different types of numpy arrays, with different uint dtypes but most of them either error or run without anything appearing on the screen does anyone know what's wrong?
When you specify the NumPy array you need to specify the type uint32. The last argument (stride) is specifies the distance in basic machine units between elements of the draw parameter array (16 bytes):
(see glMultiDrawArraysIndirect)
indirect = np.array([[3, 10, 0, 0], [3, 5, 1, 0]], dtype=np.uint32)
glMultiDrawArraysIndirect(GL_TRIANGLES, indirect, 2, 16)
or
glMultiDrawArraysIndirect(GL_TRIANGLES, indirect,
indirect.shape[0], indirect.dtype.itemsize * indirect.shape[1])
The above code does the same as:
(see glDrawArraysIndirect)
indirect1 = np.array([3, 10, 0, 0], dtype=np.uint32)
glDrawArraysIndirect(GL_TRIANGLES, indirect1)
indirect2 = np.array([3, 5, 1, 0], dtype=np.uint32)
glDrawArraysIndirect(GL_TRIANGLES, indirect2)
Respectively the same as:
(see glDrawArraysInstancedBaseInstance)
glDrawArraysInstancedBaseInstance(GL_TRIANGLES, 0, 3, 10, 0)
glDrawArraysInstancedBaseInstance(GL_TRIANGLES, 1, 3, 5, 0)
When you use glDrawArraysIndirect or glMultiDrawArraysIndirect, you need to create the following data structure:
(see GLAPI/glMultiDrawArraysIndirect)
typedef struct {
uint count;
uint instanceCount;
uint first;
uint baseInstance;
} DrawArraysIndirectCommand;
This can be achieved using a NumPy array with the data type uint32:
np.array([count, instanceCount, first, baseInstance], dtype=np.uint32)

How to render text with PyOpenGL?

I'm learning modern openGL, and at this moment I'm facing trouble with rendering text. I'm following this tutorial which is in C++, but I'm trying to implement in python.
Here is my code:
from OpenGL.GL import *
from OpenGL.GLU import *
from OpenGL.GL import shaders
import glfw
import freetype
import glm
import numpy as np
from PIL import Image
import math
import time
class CharacterSlot:
def __init__(self, texture, glyph):
self.texture = texture
self.textureSize = (glyph.bitmap.width, glyph.bitmap.rows)
if isinstance(glyph, freetype.GlyphSlot):
self.bearing = (glyph.bitmap_left, glyph.bitmap_top)
self.advance = glyph.advance.x
elif isinstance(glyph, freetype.BitmapGlyph):
self.bearing = (glyph.left, glyph.top)
self.advance = None
else:
raise RuntimeError('unknown glyph type')
def _get_rendering_buffer(xpos, ypos, w, h, zfix=0.0):
return np.asarray([
xpos, ypos - h, zfix, 0.0, 1.0,
xpos, ypos, zfix, 0.0, 0.0,
xpos + w, ypos, zfix, 1.0, 0.0,
xpos, ypos - h, zfix, 0.0, 1.0,
xpos + w, ypos, zfix, 1.0, 0.0,
xpos + w, ypos - h, zfix, 1.0, 1.0
], np.float32)
VERTEX_SHADER = """
#version 330 core
layout (location = 0) in vec4 vertex; // <vec2 pos, vec2 tex>
out vec2 TexCoords;
uniform mat4 projection;
void main()
{
gl_Position = projection * vec4(vertex.xy, 0.0, 1.0);
TexCoords = vertex.zw;
}
"""
FRAGMENT_SHADER = """
#version 330 core
in vec2 TexCoords;
out vec4 color;
uniform sampler2D text;
uniform vec3 textColor;
void main()
{
vec4 sampled = vec4(1.0, 1.0, 1.0, texture(text, TexCoords).r);
color = vec4(textColor, 1.0) * sampled;
}
"""
shaderProgram = None
Characters = dict()
VBO = None
VAO = None
def initliaze():
global VERTEXT_SHADER
global FRAGMENT_SHADER
global shaderProgram
global Characters
global VBO
global VAO
#compiling shaders
vertexshader = shaders.compileShader(VERTEX_SHADER, GL_VERTEX_SHADER)
fragmentshader = shaders.compileShader(FRAGMENT_SHADER, GL_FRAGMENT_SHADER)
#creating shaderProgram
shaderProgram = shaders.compileProgram(vertexshader, fragmentshader)
#get projection
#problem
shader_projection = glGetUniformLocation(shaderProgram, "projection")
projection = glm.ortho(0.0,640,0.0,640)
glUniformMatrix4fv(shader_projection, 1, GL_FALSE, glm.value_ptr(projection));
#disable byte-alignment restriction
glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
face = freetype.Face("Vera.ttf")
face.set_char_size( 48*64 )
#load first 128 characters of ASCII set
for i in range(0,128):
face.load_char(chr(i))
glyph = face.glyph
#generate texture
texture = glGenTextures(1)
glBindTexture(GL_TEXTURE_2D, texture)
glTexImage2D(GL_TEXTURE_2D,
0,
GL_RGB,
glyph.bitmap.width, glyph.bitmap.rows,
0,
GL_RGB,
GL_UNSIGNED_BYTE,
glyph.bitmap.buffer)
#texture options
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
#now store character for later use
Characters[chr(i)] = CharacterSlot(texture,glyph)
glBindTexture(GL_TEXTURE_2D, 0);
#configure VAO/VBO for texture quads
VAO = glGenVertexArrays(1)
glBindVertexArray(VAO)
VBO = glGenBuffers(1);
glBindBuffer(GL_ARRAY_BUFFER, VBO);
glBufferData(GL_ARRAY_BUFFER, 6 * 4, None, GL_DYNAMIC_DRAW);
glEnableVertexAttribArray(0);
glVertexAttribPointer(0, 4, GL_FLOAT, GL_FALSE, 4, 0);
glBindBuffer(GL_ARRAY_BUFFER, 0);
glBindVertexArray(0);
def render_text(window,text,x,y,scale,color):
global shaderProgram
global Characters
global VBO
global VAO
face = freetype.Face("Vera.ttf")
face.set_char_size(48*64)
glUniform3f(glGetUniformLocation(shaderProgram, "textColor"),
color[0]/255,color[1]/255,color[2]/255)
glActiveTexture(GL_TEXTURE0);
for c in text:
ch = Characters[c]
w,h = ch.textureSize
w = w*scale
h = w*scale
vertices = _get_rendering_buffer(x,y,w,h)
#render glyph texture over quad
glBindTexture(GL_TEXTURE_2D, ch.texture);
#update content of VBO memory
glBindBuffer(GL_ARRAY_BUFFER, VBO);
glBufferSubData(GL_ARRAY_BUFFER, 0, len(vertices), vertices)
glBindBuffer(GL_ARRAY_BUFFER, 0);
#render quad
glDrawArrays(GL_TRIANGLES, 0, 6);
#now advance cursors for next glyph (note that advance is number of 1/64 pixels)
x += (ch.advance+6)*scale;
glBindVertexArray(0);
glBindTexture(GL_TEXTURE_2D, 0);
glfwSwapBuffers(window);
glfwPollEvents();
def main():
glfw.init()
window = glfw.create_window(640, 640,"EXAMPLE PROGRAM",None,None)
glfw.make_context_current(window)
initliaze()
while not glfw.window_should_close(window):
glfw.poll_events()
glClearColor(0,0,0,1);
glClear(GL_COLOR_BUFFER_BIT);
render_text(window,'hello',1,1,1,(100,100,100))
glfw.terminate()
if __name__ == '__main__':
main()
I'm facing trouble in two portion so far I can understand. The first problem in initliaze(), error raised for the following portion.
shader_projection = glGetUniformLocation(shaderProgram, "projection")
projection = glm.ortho(0.0,640,0.0,640)
glUniformMatrix4fv(shader_projection, 1, GL_FALSE, glm.value_ptr(projection));
I've commented out the above portion to ignore. The second problem is in render_text() function, error raised for the following portion.
glUniform3f(glGetUniformLocation(shaderProgram, "textColor"),
color[0]/255,color[1]/255,color[2]/255)
There could be problems in many more places. I don't understand that why text rendering will be so difficult. What am I missing here?
You missed to install the shader program by glUseProgram:
shaderProgram = shaders.compileProgram(vertexshader, fragmentshader)
glUseProgram(shaderProgram) # <---
The 2nd argument to glBufferData and the 3rd argument of glBufferSubData is the size in bytes:
glBufferData(GL_ARRAY_BUFFER, 6 * 4, None, GL_DYNAMIC_DRAW)
glBufferData(GL_ARRAY_BUFFER, 6 * 4 * 4, None, GL_DYNAMIC_DRAW)
glBufferSubData(GL_ARRAY_BUFFER, 0, len(vertices), vertices)
glBufferSubData(GL_ARRAY_BUFFER, 0, vertices.nbytes, vertices)
The vertex attribute consist of a 2 dimension vertex coordinate (x, y) and a 2 dimensional texture coordinate. Remove the wird zfix from the array of vertex attribute data. Furthermore you have to flip the 2nd component of the texture coordinates (otherwise the text is upside down)
def _get_rendering_buffer(xpos, ypos, w, h, zfix=0.0):
return np.asarray([
xpos, ypos - h, 0, 0,
xpos, ypos, 0, 1,
xpos + w, ypos, 1, 1,
xpos, ypos - h, 0, 0,
xpos + w, ypos, 1, 1,
xpos + w, ypos - h, 1, 0
], np.float32)
The stride argument of glVertexAttribIPointer has to be specified in bytes. If stride is 0, the generic vertex attributes are understood to be tightly packed in the array. Hence in your case stride has to be 16 or 0:
glVertexAttribPointer(0, 4, GL_FLOAT, GL_FALSE, 4, 0)
glVertexAttribPointer(0, 4, GL_FLOAT, GL_FALSE, 0, None)
face.load_char(chr(i)) generates a image with on color channel (1 byte per pixel). Use the internal format and format GL_RED rather than GL_RGB for generating the 2 dimensional texture image:
glTexImage2D(GL_TEXTURE_2D, 0, GL_RED, glyph.bitmap.width, glyph.bitmap.rows, 0,
GL_RED, GL_UNSIGNED_BYTE, glyph.bitmap.buffer)
You have to bind the vertex array, before drawing the text:
glBindVertexArray(VAO)
for c in text:
# [...]
glDrawArrays(GL_TRIANGLES, 0, 6)
There is typo when you increment x, you have to use the >>-operator rather than the +-operator:
x += (ch.advance+6)*scale
x += (ch.advance>>6)*scale
and another typo when you compute h:
h = w*scale
h = h*scale
You have to enable alpha blending:
glEnable(GL_BLEND)
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)
In NDC (normalized device coordinates) the left bottom is (-1, -1) and the right top is (1, 1). Set the orthographic projection in that way, that the top left of the window is at (0, 0):
projection = glm.ortho(0.0,640,0.0,640)
projection = glm.ortho(0, 640, 640, 0)
The reference point of the text is at the bottom. Hence you have to set a x coordinate greater than the text height:
render_text(window,'hello',1,1,1,(100,100,100))
render_text(window,'hello', 20, 50, 1, (255, 100, 100))
See the complete example (I've used a different font):
from OpenGL.GL import *
from OpenGL.GLU import *
from OpenGL.GL import shaders
import glfw
import freetype
import glm
import numpy as np
from PIL import Image
import math
import time
fontfile = "Vera.ttf"
#fontfile = r'C:\source\resource\fonts\gnu-freefont_freesans\freesans.ttf'
class CharacterSlot:
def __init__(self, texture, glyph):
self.texture = texture
self.textureSize = (glyph.bitmap.width, glyph.bitmap.rows)
if isinstance(glyph, freetype.GlyphSlot):
self.bearing = (glyph.bitmap_left, glyph.bitmap_top)
self.advance = glyph.advance.x
elif isinstance(glyph, freetype.BitmapGlyph):
self.bearing = (glyph.left, glyph.top)
self.advance = None
else:
raise RuntimeError('unknown glyph type')
def _get_rendering_buffer(xpos, ypos, w, h, zfix=0.0):
return np.asarray([
xpos, ypos - h, 0, 0,
xpos, ypos, 0, 1,
xpos + w, ypos, 1, 1,
xpos, ypos - h, 0, 0,
xpos + w, ypos, 1, 1,
xpos + w, ypos - h, 1, 0
], np.float32)
VERTEX_SHADER = """
#version 330 core
layout (location = 0) in vec4 vertex; // <vec2 pos, vec2 tex>
out vec2 TexCoords;
uniform mat4 projection;
void main()
{
gl_Position = projection * vec4(vertex.xy, 0.0, 1.0);
TexCoords = vertex.zw;
}
"""
FRAGMENT_SHADER = """
#version 330 core
in vec2 TexCoords;
out vec4 color;
uniform sampler2D text;
uniform vec3 textColor;
void main()
{
vec4 sampled = vec4(1.0, 1.0, 1.0, texture(text, TexCoords).r);
color = vec4(textColor, 1.0) * sampled;
}
"""
shaderProgram = None
Characters = dict()
VBO = None
VAO = None
def initliaze():
global VERTEXT_SHADER
global FRAGMENT_SHADER
global shaderProgram
global Characters
global VBO
global VAO
#compiling shaders
vertexshader = shaders.compileShader(VERTEX_SHADER, GL_VERTEX_SHADER)
fragmentshader = shaders.compileShader(FRAGMENT_SHADER, GL_FRAGMENT_SHADER)
#creating shaderProgram
shaderProgram = shaders.compileProgram(vertexshader, fragmentshader)
glUseProgram(shaderProgram)
#get projection
#problem
shader_projection = glGetUniformLocation(shaderProgram, "projection")
projection = glm.ortho(0, 640, 640, 0)
glUniformMatrix4fv(shader_projection, 1, GL_FALSE, glm.value_ptr(projection))
#disable byte-alignment restriction
glPixelStorei(GL_UNPACK_ALIGNMENT, 1)
face = freetype.Face(fontfile)
face.set_char_size( 48*64 )
#load first 128 characters of ASCII set
for i in range(0,128):
face.load_char(chr(i))
glyph = face.glyph
#generate texture
texture = glGenTextures(1)
glBindTexture(GL_TEXTURE_2D, texture)
glTexImage2D(GL_TEXTURE_2D, 0, GL_RED, glyph.bitmap.width, glyph.bitmap.rows, 0,
GL_RED, GL_UNSIGNED_BYTE, glyph.bitmap.buffer)
#texture options
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE)
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE)
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR)
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR)
#now store character for later use
Characters[chr(i)] = CharacterSlot(texture,glyph)
glBindTexture(GL_TEXTURE_2D, 0)
#configure VAO/VBO for texture quads
VAO = glGenVertexArrays(1)
glBindVertexArray(VAO)
VBO = glGenBuffers(1)
glBindBuffer(GL_ARRAY_BUFFER, VBO)
glBufferData(GL_ARRAY_BUFFER, 6 * 4 * 4, None, GL_DYNAMIC_DRAW)
glEnableVertexAttribArray(0)
glVertexAttribPointer(0, 4, GL_FLOAT, GL_FALSE, 0, None)
glBindBuffer(GL_ARRAY_BUFFER, 0)
glBindVertexArray(0)
def render_text(window,text,x,y,scale,color):
global shaderProgram
global Characters
global VBO
global VAO
face = freetype.Face(fontfile)
face.set_char_size(48*64)
glUniform3f(glGetUniformLocation(shaderProgram, "textColor"),
color[0]/255,color[1]/255,color[2]/255)
glActiveTexture(GL_TEXTURE0)
glEnable(GL_BLEND)
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)
glBindVertexArray(VAO)
for c in text:
ch = Characters[c]
w, h = ch.textureSize
w = w*scale
h = h*scale
vertices = _get_rendering_buffer(x,y,w,h)
#render glyph texture over quad
glBindTexture(GL_TEXTURE_2D, ch.texture)
#update content of VBO memory
glBindBuffer(GL_ARRAY_BUFFER, VBO)
glBufferSubData(GL_ARRAY_BUFFER, 0, vertices.nbytes, vertices)
glBindBuffer(GL_ARRAY_BUFFER, 0)
#render quad
glDrawArrays(GL_TRIANGLES, 0, 6)
#now advance cursors for next glyph (note that advance is number of 1/64 pixels)
x += (ch.advance>>6)*scale
glBindVertexArray(0)
glBindTexture(GL_TEXTURE_2D, 0)
glfw.swap_buffers(window)
glfw.poll_events()
def main():
glfw.init()
window = glfw.create_window(640, 640,"EXAMPLE PROGRAM",None,None)
glfw.make_context_current(window)
initliaze()
while not glfw.window_should_close(window):
glfw.poll_events()
glClearColor(0,0,0,1)
glClear(GL_COLOR_BUFFER_BIT)
render_text(window,'hello', 20, 50, 1, (255, 100, 100))
glfw.terminate()
if __name__ == '__main__':
main()
See also FreeType / OpenGL text rendering

Inconsistent skybox rendering using different textures in Pygame + PyOpenGL

Motivated by my incomplete answer to this question, I am implementing a simple skybox in PyOpenGL in accordance with this tutorial, making minor tweaks as needed for OpenGL 2.1/GLSL 120 and python2.7-isms. For the most part, it works successfully, but depending on what six images I pass to my cubemap, the images either end up swapped between a single pair of opposite faces or are randomly rotated! Below is the main class of this demo:
import pygame
import sys
import time
import glob
import numpy as np
from ctypes import *
from OpenGL.GL import *
from OpenGL.GL import shaders
from OpenGL.GLU import *
def load_shaders(vert_url, frag_url):
vert_str = "\n".join(open(vert_url).readlines())
frag_str = "\n".join(open(frag_url).readlines())
vert_shader = shaders.compileShader(vert_str, GL_VERTEX_SHADER)
frag_shader = shaders.compileShader(frag_str, GL_FRAGMENT_SHADER)
program = shaders.compileProgram(vert_shader, frag_shader)
return program
def load_cubemap(folder_url):
tex_id = glGenTextures(1)
face_order = ["right", "left", "top", "bottom", "back", "front"]
"""
#hack that fixes issues for ./images1/
face_order = ["right", "left", "top", "bottom", "front", "back"]
"""
face_urls = sorted(glob.glob(folder_url + "*"))
glActiveTexture(GL_TEXTURE0)
glBindTexture(GL_TEXTURE_CUBE_MAP, tex_id)
for i, face in enumerate(face_order):
face_url = [face_url for face_url in face_urls if face in face_url.lower()][0]
face_image = pygame.image.load(face_url).convert()
"""
#hack that fixes issues for ./images2/
if face == "bottom":
face_image = pygame.transform.rotate(face_image, 270)
if face == "top":
face_image = pygame.transform.rotate(face_image, 90)
"""
"""
#hack that fixes issues for ./images3/
if face == "bottom" or face == "top":
face_image = pygame.transform.rotate(face_image, 180)
"""
face_surface = pygame.image.tostring(face_image, 'RGB')
face_width, face_height = face_image.get_size()
glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X + i, 0, GL_RGB, face_width, face_height, 0, GL_RGB, GL_UNSIGNED_BYTE, face_surface)
glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MAG_FILTER, GL_LINEAR)
glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MIN_FILTER, GL_LINEAR)
glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE)
glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE)
glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_EDGE)
glBindTexture(GL_TEXTURE_CUBE_MAP, 0)
return tex_id
def render():
global width, height, program
global rotation, cubemap
glEnable(GL_DEPTH_TEST)
glEnable(GL_TEXTURE_2D)
glEnable(GL_TEXTURE_CUBE_MAP)
skybox_right = [1, -1, -1, 1, -1, 1, 1, 1, 1, 1, 1, 1, 1, 1, -1, 1, -1, -1]
skybox_left = [-1, -1, 1, -1, -1, -1, -1, 1, -1, -1, 1, -1, -1, 1, 1, -1, -1, 1]
skybox_top = [-1, 1, -1, 1, 1, -1, 1, 1, 1, 1, 1, 1, -1, 1, 1, -1, 1, -1]
skybox_bottom = [-1, -1, -1, -1, -1, 1, 1, -1, -1, 1, -1, -1, -1, -1, 1, 1, -1, 1]
skybox_back = [-1, 1, -1, -1, -1, -1, 1, -1, -1, 1, -1, -1, 1, 1, -1, -1, 1, -1]
skybox_front = [-1, -1, 1, -1, 1, 1, 1, 1, 1, 1, 1, 1, 1, -1, 1, -1, -1, 1]
skybox_vertices = np.array([skybox_right, skybox_left, skybox_top, skybox_bottom, skybox_back, skybox_front], dtype=np.float32).flatten()
skybox_vbo = glGenBuffers(1)
glBindBuffer(GL_ARRAY_BUFFER, skybox_vbo)
glBufferData(GL_ARRAY_BUFFER, skybox_vertices.nbytes, skybox_vertices, GL_STATIC_DRAW)
glBindBuffer(GL_ARRAY_BUFFER, 0)
glClear(GL_COLOR_BUFFER_BIT)
glClear(GL_DEPTH_BUFFER_BIT)
glMatrixMode(GL_PROJECTION)
glLoadIdentity()
gluPerspective(60, float(width)/height, 0.1, 1000)
glMatrixMode(GL_MODELVIEW)
glLoadIdentity()
#glRotate(rotation, 0, 1, 0)#spin around y axis
#glRotate(rotation, 1, 0, 0)#spin around x axis
glRotate(rotation, 1, 1, 1)#rotate around x, y, and z axes
glUseProgram(program)
glDepthMask(GL_FALSE)
glBindTexture(GL_TEXTURE_CUBE_MAP, cubemap)
glEnableClientState(GL_VERTEX_ARRAY)
glBindBuffer(GL_ARRAY_BUFFER, skybox_vbo)
glVertexPointer(3, GL_FLOAT, 0, None)
glDrawArrays(GL_TRIANGLES, 0, 36)
glBindBuffer(GL_ARRAY_BUFFER, 0)
glDisableClientState(GL_VERTEX_ARRAY)
glBindTexture(GL_TEXTURE_CUBE_MAP, 0)
glDepthMask(GL_TRUE)
glUseProgram(0)
pygame.display.flip()
if __name__ == "__main__":
title = "Skybox"
target_fps = 60
(width, height) = (800, 600)
flags = pygame.DOUBLEBUF|pygame.OPENGL
screen = pygame.display.set_mode((width, height), flags)
prev_time = time.time()
rotation = 0
cubemap = load_cubemap("./images1/")#front and back images appear swapped
#cubemap = load_cubemap("./images2/")#top and bottom images appear rotated by 90 and 270 degrees respectively
#cubemap = load_cubemap("./images3/")#top and bottom images appear rotated by 180 degrees
program = load_shaders("./shaders/skybox.vert", "./shaders/skybox.frag")
pause = False
while True:
#Handle the events
for event in pygame.event.get():
if event.type == pygame.QUIT:
sys.exit()
elif event.type == pygame.KEYDOWN:
if event.key == pygame.K_SPACE:
pause = not pause
#Do computations and render stuff on screen
if not pause:
rotation += 1
render()
#Handle timing code for desired FPS
curr_time = time.time()
diff = curr_time - prev_time
delay = max(1.0/target_fps - diff, 0)
time.sleep(delay)
fps = 1.0/(delay + diff)
prev_time = curr_time
pygame.display.set_caption("{0}: {1:.2f}".format(title, fps))
I use the following vertex and fragment shaders for displaying the cubemaps for the skybox:
./shaders/skybox.vert
#version 120
varying vec3 tex_coords;
void main()
{
gl_Position = gl_ProjectionMatrix * gl_ModelViewMatrix * gl_Vertex;
tex_coords = vec3(gl_Vertex);
}
./shaders/skybox.frag
#version 120
varying vec3 tex_coords;
uniform samplerCube skybox;
void main()
{
gl_FragColor = textureCube(skybox, tex_coords);
}
I believe after much playing around that the error is in pygame's loading of the skybox images. I have tested three sets of skybox images. Each one has a different visual error and hack to fix them, which I have noted in the above code. Here are the sources for the three skyboxes for testing (be sure to rename the images so that they include right, left, top, bottom, back, or front in their respective file names).
./images1/: here
./images2/: here
./images3/: here (using the "rays" images in this zip)
All of these three skyboxes use different image formats (bmp, tga, and png respectively). How can I consistently handle all of these and future image cases robustly without relying on seemingly random rotations or image swaps? Any help or insight would be greatly appreciated.
Update:
I have created a github repository where you can test out the code without having to create a main.py and shaders, download the images, and rename and organize the contents yourself. This should make the code a lot easier to run in case you are interested in testing it out.
Here are the versions of everything that I am using:
python 2.7.12
pygame 1.9.2b1
pyopengl 3.1.0 (using opengl 2.1 and GLSL 120)
Let me know if you need any other information!
So, it turns out that all of the issues that I was having rendering the skybox could be boiled down to two causes, none of which were due to inconsistencies in how pygame loads images of various file formats!
The skybox images were inconsistent with one another in how the seams between two faces of the cubes were attached. This explained why each skybox image test result had different issues. Following the convention of being inside the cube describe in this question, I flipped and resaved the images in paint.
That alone was not enough, however. It turns out that the OpenGL convention for the z-axis in "cubemap-land" is flipped. This caused the front and back faces to be swapped with one another. The simplest fix that I could come up with is swapping the texture coordinates in the vertex shader. Here is the corrected vertex shader.
#version 120
varying vec3 tex_coords;
void main()
{
gl_Position = gl_ProjectionMatrix * gl_ModelViewMatrix * gl_Vertex;
tex_coords = vec3(gl_Vertex) * vec3(1, 1, -1);
}
I have the code in the github mentioned in the question to reflect these changes as well as improve the camera for manually looking around.
Here is an animated gif of the final result for anyone who is interested!

Visualizing a 3D NumPy array with PyOpenGL

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.

Categories