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()
Related
The app is based on PyOpenGL (core profile) and using orthographic projection. I have to draw several different 2d shapes on a quad(2 triangles).
I have found a really great article on rendering 2d/3d shapes using SDF. The first shape I'm trying is a rounded rectangle with border. This Shadertoy example perfectly fits to my requirement. Here are my two shaders:
VERTEX SHADER
#version 330 core
// VERTEX SHADER
layout (location = 0) in vec3 aPos;
layout (location = 1) in vec2 aTexCoord;
out vec2 tex_coord;
uniform mat4 mvp;
void main()
{
gl_Position = mvp * vec4(aPos, 1.0);
tex_coord = aTexCoord;
}
FRAGMENT SHADER
#version 330 core
// FRAGMENT SHADER
uniform vec4 in_color;
in vec2 tex_coord;
vec2 resolution = vec2(800, 600);
float aspect = resolution.x / resolution.y;
const float borderThickness = 0.01;
const vec4 borderColor = vec4(1.0, 1.0, 0.0, 1.0);
const vec4 fillColor = vec4(1.0, 0.0, 0.0, 1.0);
const float radius = 0.05;
float RectSDF(vec2 p, vec2 b, float r)
{
vec2 d = abs(p) - b + vec2(r);
return min(max(d.x, d.y), 0.0) + length(max(d, 0.0)) - r;
}
void main() {
// https://www.shadertoy.com/view/ltS3zW
vec2 centerPos = tex_coord - vec2(0.5, 0.5); // <-0.5,0.5>
//vec2 centerPos = (tex_coord/resolution - vec2(0.5)) * 2.0;
//centerPos *= aspect; // fix aspect ratio
//centerPos = (centerPos - resolution.xy) * 2.0;
float fDist = RectSDF(centerPos, vec2(0.5, 0.5), radius);
vec4 v4FromColor = borderColor; // Always the border color. If no border, this still should be set
vec4 v4ToColor = vec4(0.0, 0.0, 1.0, 1.0); // Outside color
if (borderThickness > 0.0)
{
if (fDist < 0.0)
{
v4ToColor = fillColor;
}
fDist = abs(fDist) - borderThickness;
}
float fBlendAmount = smoothstep(-0.01, 0.01, fDist);
// final color
gl_FragColor = mix(v4FromColor, v4ToColor, fBlendAmount);
}
And the difference between two outputs:
Problem 1
In Shadertoy example, border is neat and there is no blurring, mine is blurred.
Problem 2
I am using ndc coordinates to specify borderThickness and radius, because of this I'm not getting a consistent border. If you see in the image, horizontal border is slightly wider then vertical one. I would prefer to use borderThickness and radius in pixel size. The idea is to get a consistent border around the rectangle irrespective of screen dimension.
Problem 3
Make outside blue color transparent.
Problem 4
As I've mentioned that I've recently started to learn GLSL, Some where I've read that too many "if" conditions would greatly affect the shader performance and chances are you might be using them unnecessary. There are already two "if" conditions exists in this code and I'm not sure if they can be omitted.
Use a Uniform (rectSize) to to specify the size of the rectangle in pixel. The texture coordinates (tex_coord) need to be in range [0.0, 1.0]. Compute the pixel position in the rectangle (rectSize * tex_coord). Now you can specify the radius and the edge thickness in pixels:
in vec2 tex_coord;
uniform vec2 rectSize;
const float borderThickness = 10.0;
const float radius = 30.0;
// [...]
void main()
{
vec2 pos = rectSize * tex_coord;
float fDist = RectSDF(pos-rectSize/2.0, rectSize/2.0 - borderThickness/2.0-1.0, radius);
float fBlendAmount = smoothstep(-1.0, 1.0, abs(fDist) - borderThickness / 2.0);
vec4 v4FromColor = borderColor;
vec4 v4ToColor = (fDist < 0.0) ? fillColor : vec4(0.0);
gl_FragColor = mix(v4FromColor, v4ToColor, fBlendAmount);
}
rect_loc = glGetUniformLocation(program, "rectSize")
glUniform2f(rect_loc, width, height)
Use Blending to make the outside transparent. For this, the alpha channel of the outer color must be 0. (e.g. vec4(0.0))
glEnable(GL_BLEND)
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)
Minimal example:
from OpenGL.GLUT import *
from OpenGL.GLU import *
from OpenGL.GL import *
import OpenGL.GL.shaders
from ctypes import c_void_p
import glm
sh_vert = """
#version 330 core
// VERTEX SHADER
layout (location = 0) in vec3 aPos;
layout (location = 1) in vec2 aTexCoord;
out vec2 tex_coord;
uniform mat4 mvp;
void main()
{
gl_Position = mvp * vec4(aPos, 1.0);
tex_coord = aTexCoord;
}
"""
sh_frag = """
#version 330 core
// FRAGMENT SHADER
in vec2 tex_coord;
uniform vec2 rectSize;
const vec4 borderColor = vec4(1.0, 1.0, 0.0, 1.0);
const vec4 fillColor = vec4(1.0, 0.0, 0.0, 1.0);
const float borderThickness = 10.0;
const float radius = 30.0;
float RectSDF(vec2 p, vec2 b, float r)
{
vec2 d = abs(p) - b + vec2(r);
return min(max(d.x, d.y), 0.0) + length(max(d, 0.0)) - r;
}
void main()
{
vec2 pos = rectSize * tex_coord;
float fDist = RectSDF(pos-rectSize/2.0, rectSize/2.0 - borderThickness/2.0-1.0, radius);
float fBlendAmount = smoothstep(-1.0, 1.0, abs(fDist) - borderThickness / 2.0);
vec4 v4FromColor = borderColor;
vec4 v4ToColor = (fDist < 0.0) ? fillColor : vec4(0.0);
gl_FragColor = mix(v4FromColor, v4ToColor, fBlendAmount);
}
"""
def display():
glClear(GL_COLOR_BUFFER_BIT)
glEnable(GL_BLEND)
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)
glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, None)
glutSwapBuffers()
glutPostRedisplay()
def reshape(width, height):
glViewport(0, 0, width, height)
resolution = (640, 480)
rect = (50, 50, 350, 250)
attributes = (GLfloat * 20)(*[rect[0],rect[1],0,0,1, rect[2],rect[1],0,1,1, rect[2],rect[3],0,1,0, rect[0],rect[3],0,0,0])
indices = (GLuint * 6)(*[0,1,2, 0,2,3])
glutInit(sys.argv)
glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGB)
glutInitWindowSize(*resolution)
glutCreateWindow(b"OpenGL Window")
glutDisplayFunc(display)
glutReshapeFunc(reshape)
vao = glGenVertexArrays(1)
vbo = glGenBuffers(1)
ebo = glGenBuffers(1)
glBindVertexArray(vao)
glBindBuffer(GL_ARRAY_BUFFER, vbo)
glBufferData(GL_ARRAY_BUFFER, attributes, GL_STATIC_DRAW)
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ebo)
glBufferData(GL_ELEMENT_ARRAY_BUFFER, indices, GL_STATIC_DRAW)
glVertexAttribPointer(0, 3, GL_FLOAT, False, 5 * 4, None)
glEnableVertexAttribArray(0)
glVertexAttribPointer(1, 2, GL_FLOAT, False, 5 * 4, c_void_p(3 * 4))
glEnableVertexAttribArray(1)
program = OpenGL.GL.shaders.compileProgram(
OpenGL.GL.shaders.compileShader(sh_vert, GL_VERTEX_SHADER),
OpenGL.GL.shaders.compileShader(sh_frag, GL_FRAGMENT_SHADER)
)
glUseProgram(program)
mvp_loc = glGetUniformLocation(program, "mvp")
mvp = glm.ortho(0, *resolution, 0, -1, 1)
glUniformMatrix4fv(mvp_loc, 1, GL_FALSE, glm.value_ptr(mvp))
rect_loc = glGetUniformLocation(program, "rectSize")
glUniform2f(rect_loc, rect[2]-rect[0], rect[3]-rect[1])
glClearColor(0.5, 0.5, 0.5, 0.0)
glutMainLoop()
I've been facing an issue for the past couple of days, and I still haven't been able to figure it out.. I'm trying to port a previous C++ Opengl project to PyOpengl, but I'm not able to get the object to render as it should. I'm simply trying to render a 3D grid model, which works in the original C++ code, but not in Python PyOpenGL.
What it should look like: (C++ OpenGL)
What it looks like (Python PyOpenGL)
This is the code I end up with in PyOpenGL :
import glfw
import glm
import numpy as np
from OpenGL.GL import *
import Shader
# Settings
SCR_WIDTH = 800
SCR_HEIGHT = 600
def framebuffer_size_callback(window, width, height):
if width != 0 and height != 0:
width = width
height = height
glViewport(0, 0, width, height)
def create_v_array():
vertexArray = []
indexArray = []
for x in range(-100, 102, 2):
# To draw lines across x axis from z = -1 to z = 1
vertexArray.append(glm.vec3(x / 100.0, 0.0, -1.0)) # Vertex position 1
vertexArray.append(glm.vec3(1.0, 1.0, 1.0)) # color for v1
vertexArray.append(glm.vec3(x / 100.0, 0.0, 1.0)) # Vertex position 2
vertexArray.append(glm.vec3(1.0, 1.0, 1.0)) # color for v2
for z in range(-100, 102, 2):
# To draw lines across z axis from x = -1 to x = 1
vertexArray.append(glm.vec3(-1.0, 0.0, z / 100.0)) # Vertex position 1
vertexArray.append(glm.vec3(1.0, 1.0, 1.0)) # color for v1
vertexArray.append(glm.vec3(1.0, 0.0, z / 100.0)) # Vertex position 2
vertexArray.append(glm.vec3(1.0, 1.0, 1.0)) # color for v2
for i in range(10000):
indexArray.append(i)
vao = GLuint()
vbo = GLuint()
ebo = GLuint()
vertexArray = np.array(vertexArray, dtype=np.float32)
indexArray = np.array(indexArray, dtype=np.float32)
# Bind vao
glGenVertexArrays(1, vao)
glBindVertexArray(vao)
# Upload Vertex Buffer (VBO) to the GPU, keep a reference to it (vertexBufferObject)
glGenBuffers(1, vbo)
glBindBuffer(GL_ARRAY_BUFFER, vbo)
glBufferData(GL_ARRAY_BUFFER, vertexArray.nbytes, vertexArray, GL_STATIC_DRAW)
# Upload Index Buffer (EBO) to the GPU, keep a reference to it (elementBufferObject)
glGenBuffers(1, ebo)
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ebo)
glBufferData(GL_ELEMENT_ARRAY_BUFFER, indexArray.nbytes, indexArray, GL_STATIC_DRAW)
# Position
glVertexAttribPointer(0,
3,
GL_FLOAT,
GL_FALSE,
24,
None
)
glEnableVertexAttribArray(0)
# Color
glVertexAttribPointer(1,
3,
GL_FLOAT,
GL_FALSE,
24,
ctypes.c_void_p(12)
)
glEnableVertexAttribArray(1)
glBindBuffer(GL_ARRAY_BUFFER, 0)
glBindVertexArray(0)
return vao
def main():
# Initialize the library
#glfw.glewExperimental = GL_TRUE
if not glfw.init():
return
glfw.window_hint(glfw.CONTEXT_VERSION_MAJOR, 3)
glfw.window_hint(glfw.CONTEXT_VERSION_MINOR, 2)
glfw.window_hint(glfw.OPENGL_PROFILE, glfw.OPENGL_CORE_PROFILE)
glfw.window_hint(glfw.OPENGL_FORWARD_COMPAT, GL_TRUE)
# Create a windowed mode window and its OpenGL context
window = glfw.create_window(SCR_WIDTH, SCR_HEIGHT, "OpenGL", None, None)
if not window:
glfw.terminate()
return
# Make the window's context current
glfw.make_context_current(window)
glfw.set_framebuffer_size_callback(window, framebuffer_size_callback)
#glEnable(GL_DEPTH_TEST)
#glDepthFunc(GL_LESS)
shader = Shader.Shader("VertexShader.vsh", "FragmentShader.fsh")
vao = create_v_array()
glUseProgram(shader.ID)
# Loop until the user closes the window
while not glfw.window_should_close(window):
glClearColor(0.2, 0.3, 0.3, 1.0)
glClear(GL_COLOR_BUFFER_BIT)
glUseProgram(shader.ID)
glBindVertexArray(vao)
model = glm.mat4(1)
view = glm.mat4(1)
model = glm.rotate(model, glm.radians(15.0), glm.vec3(1.0, 0.0, 0.0))
view = glm.translate(view, glm.vec3(0.0, 0.0, -1.0))
projection = glm.perspective(glm.radians(45.0), (SCR_WIDTH/SCR_HEIGHT), 0.1, 100.0)
shader.set_mat4("model", model)
shader.set_mat4("view", view)
shader.set_mat4("projection", projection)
glBindVertexArray(vao)
glDrawElements(GL_LINES, 20000, GL_UNSIGNED_INT, None)
glfw.swap_buffers(window)
glfw.poll_events()
glfw.terminate()
if __name__ == "__main__":
main()
Custom Shader class:
class Shader:
def __init__(self, vertex_path, fragment_path):
vertex_code = get_file_content(vertex_path)
fragment_code = get_file_content(fragment_path)
vertex = glCreateShader(GL_VERTEX_SHADER)
glShaderSource(vertex, vertex_code)
glCompileShader(vertex)
result = glGetShaderiv(vertex, GL_COMPILE_STATUS)
if not (result):
print("Vertex shader ERROR!")
raise RuntimeError(glGetShaderInfoLog(vertex))
fragment = glCreateShader(GL_FRAGMENT_SHADER)
glShaderSource(fragment, fragment_code)
glCompileShader(fragment)
result2 = glGetShaderiv(fragment, GL_COMPILE_STATUS)
if not (result2):
print("Fragment shader ERROR!")
raise RuntimeError(glGetShaderInfoLog(vertex))
self.ID = glCreateProgram()
glAttachShader(self.ID, vertex)
glAttachShader(self.ID, fragment)
glLinkProgram(self.ID)
success = glGetProgramiv(self.ID, GL_LINK_STATUS)
if not success:
print("gad darn")
infolog = glGetProgramInfoLog(self.ID)
print("ERROR::SHADER::PROGRAM::LINKING_FAILED\n", infolog)
glDeleteShader(vertex)
glDeleteShader(fragment)
def set_mat4(self, name, matrix):
glUniformMatrix4fv(glGetUniformLocation(self.ID, name), 1, GL_FALSE, glm.value_ptr(matrix))
def use(self):
glUseProgram(self.ID)
# Helper Function
def get_file_content(file):
with open(file) as f:
content = f.read()
return content
glsl files:
Vertex Shader:
#version 330 core
layout (location = 0) in vec3 aPos;
layout (location = 1) in vec3 aColor;
out vec3 vertexColor;
uniform mat4 model;
uniform mat4 view;
uniform mat4 projection;
void main()
{
gl_Position = projection * view * model * vec4(aPos, 1.0);
vertexColor = aColor;
}
Fragment Shader:
#version 330 core
in vec3 vertexColor;
out vec4 FragColor;
void main()
{
FragColor = vec4(vertexColor.r, vertexColor.g, vertexColor.b, 1.0f);
}
The type of the indices must be integral instead of the floating point:
indexArray = np.array(indexArray, dtype=np.float32)
indexArray = np.array(indexArray, dtype=np.uint32)
Relatively simple program drawing a blank screen. Ported from Superbible Opengl 7th ed.
The program is currently drawing a blank screen. Although the textoverlay is working correctly.
Update: Added the use of glGetError to add additional error checking. Though there is no error produced by it. Still a blank screen however.
Update and SUCCESS: The program is now rendering the hdr image perfect. Thanks to Rabbid76 excellent answer. It was also a ktxloader that needed a fix. Thank you.
Expected output is:
support files: hdrexposure_support.zip
ported from: hdrexposure.cpp
source code:
#!/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 # location of ktx file format loader
from textoverlay import OVERLAY_
from shader import shader_load, link_from_shaders
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
import glm
identityMatrix = [1,0,0,0, 0,1,0,0, 0,0,1,0, 0,0,0,1]
myobject = SBMObject()
ktxobject = KTXObject()
overlay = OVERLAY_()
texture = GLuint(0)
program = GLuint(0)
vao = GLuint(0)
exposure=1.0
vs_source = '''
#version 420 core
void main(void)
{
const vec4 vertices[] = vec4[](vec4(-1.0, -1.0, 0.5, 1.0),
vec4( 1.0, -1.0, 0.5, 1.0),
vec4(-1.0, 1.0, 0.5, 1.0),
vec4( 1.0, 1.0, 0.5, 1.0));
gl_Position = vertices[gl_VertexID];
}
'''
fs_source = '''
#version 430 core
uniform sampler2D s;
uniform float exposure;
out vec4 color;
void main(void)
{
vec4 c = texture(s, gl_FragCoord.xy / vec2(512.0, 512.0));
c.xyz = vec3(1.0) - exp(-c.xyz * exposure);
color = c;
}
'''
def checkGLError():
status = glGetError()
if status != GL_NO_ERROR:
raise RuntimeError('gl error %s' % (status,))
class Scene:
def __init__(self, width, height):
global overlay
global texture
global program
global vao
self.width = width
self.height = height
overlay.init(80, 50)
#// Generate a name for the texture
glGenTextures(1, texture)
#// Load texture from file
texture = ktxobject.ktx_load("treelights_2k.ktx")
#// Now bind it to the context using the GL_TEXTURE_2D binding point
glBindTexture(GL_TEXTURE_2D, texture)
program = glCreateProgram()
fs = glCreateShader(GL_FRAGMENT_SHADER)
glShaderSource(fs, fs_source)
glCompileShader(fs)
if not glGetShaderiv(fs, GL_COMPILE_STATUS):
print( 'compile error:' )
print( glGetShaderInfoLog(fs) )
vs = glCreateShader(GL_VERTEX_SHADER)
glShaderSource(vs, vs_source)
glCompileShader(vs)
if not glGetShaderiv(vs, GL_COMPILE_STATUS):
print( 'compile error:' )
print( glGetShaderInfoLog(vs) )
glAttachShader(program, vs)
glAttachShader(program, fs)
glLinkProgram(program)
if not glGetProgramiv(program, GL_LINK_STATUS):
print( 'link error:' )
print( glGetProgramInfoLog(program) )
glGenVertexArrays(1, vao)
glBindVertexArray(vao)
def display(self):
global texture
global program
currentTime = time.time()
green = [ 0.0, 0.25, 0.0, 1.0 ]
glClearBufferfv(GL_COLOR, 0, green)
glActiveTexture(GL_TEXTURE0)
glBindTexture(GL_TEXTURE_2D, texture)
glUseProgram(program)
glViewport(0, 0, self.width, self.height)
glUniform1f(0, exposure)
glDrawArrays(GL_TRIANGLE_STRIP, 0, 4)
overlay.clear()
buffer = ("Exposure = %2.2f (Numpad +/- to change)" % exposure)
overlay.drawText(buffer, 0, 0)
overlay.draw()
checkGLError()
glutSwapBuffers()
def reshape(self, width, height):
self.width = width
self.height = height
def keyboard(self, key, x, y ):
global fullscreen
global exposure
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'+':
exposure *= 1.1
elif key == b'-':
exposure /= 1.1
def init(self):
pass
def timer(self, blah):
glutPostRedisplay()
glutTimerFunc( int(1/60), self.timer, 0)
time.sleep(1/60.0)
if __name__ == '__main__':
glutInit()
glutInitDisplayMode(GLUT_RGBA | GLUT_DOUBLE | GLUT_DEPTH)
glutInitWindowSize(512, 512)
w1 = glutCreateWindow('OpenGL SuperBible - HDR Exposure')
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()
Current output:
I was getting before updating the fragment shader:
Any help is appreciated. Thank You.
The result of exp(0.0) is 1.0, so the result of 1.0-exp(0.0) is 0.0.
You've to ensure that the value of the uniform exposure is not 0.0, to get a result greater than 0.0 for the expression:
c.xyz = vec3(1.0) - exp(-c.xyz * exposure);
The texture data is of type float. Create numpy.array with the element type numpy.float32, from the byte array, before initializing the texture image:
for i in range(0, h.miplevels):
if h.gltype == GL_FLOAT:
float_data = np.frombuffer(data[ptr:], dtype=np.float32)
glTexSubImage2D(GL_TEXTURE_2D, i, 0, 0, width, height, h.glformat, h.gltype, float_data)
else: # h.type == GL_UNSIGNED_BYTE
glTexSubImage2D(GL_TEXTURE_2D, i, 0, 0, width, height, h.glformat, h.gltype, data[ptr:])
ptr += height * calculate_stride(h, width, 1)
Note, you've to implement different cases for different types.
I mostly ported over the dragon example from SB OpenGL. The output gif of the program is below the code.
My question what is what's the lookat function in python?
Supporting files: dragon.zip simply put pydragon.py into the folder 'dragon' and run
Source code of pydragon.py
#!/usr/bin/python3
import sys
import time
sys.path.append("./shared")
from sbmloader import SBMObject # location of sbm file format loader
from ktxloader import KTXObject # location of ktx file format loader
from sbmath import m3dDegToRad, m3dRadToDeg, m3dTranslateMatrix44, m3dRotationMatrix44, m3dMultiply, m3dOrtho, m3dPerspective, rotation_matrix, translate, m3dScaleMatrix44
fullscreen = True
import numpy.matlib
import numpy as np
import math
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()
identityMatrix = [1,0,0,0, 0,1,0,0, 0,0,1,0, 0,0,0,1]
clear_program = GLuint(0)
append_program = GLuint(0)
resolve_program = GLuint(0)
class textures:
color = GLuint(0)
normals = GLuint(0)
class uniforms_block:
mv_matrix = (GLfloat * 16)(*identityMatrix)
view_matrix = (GLfloat * 16)(*identityMatrix)
proj_matrix = (GLfloat * 16)(*identityMatrix)
uniforms_buffer = GLuint(0)
class uniforms:
mvp = GLuint(0)
fragment_buffer = GLuint(0)
head_pointer_image = GLuint(0)
atomic_counter_buffer = GLuint(0)
dummy_vao = GLuint(0)
uniform = uniforms()
myobject = SBMObject()
def length(v):
return math.sqrt(v[0]*v[0]+v[1]*v[1]+v[2]*v[2])
def normalize(v):
l = length(v)
#if (v[0] == 0 and v[1] == 0 and v[2] ==0):
# return [0.0, 1/3, 0.0]
return [v[0]/l, v[1]/l, v[2]/l]
def dot(v0, v1):
return v0[0]*v1[0]+v0[1]*v1[1]+v0[2]*v1[2]
def cross(v0, v1):
return [
v0[1]*v1[2]-v1[1]*v0[2],
v0[2]*v1[0]-v1[2]*v0[0],
v0[0]*v1[1]-v1[0]*v0[1]]
def m3dLookAt(eye, target, up):
mz = normalize( (eye[0]-target[0], eye[1]-target[1], eye[2]-target[2]) ) # inverse line of sight
mx = normalize( cross( up, mz ) )
my = normalize( cross( mz, mx ) )
tx = dot( mx, eye )
ty = dot( my, eye )
tz = -dot( mz, eye )
return np.array([mx[0], my[0], mz[0], 0, mx[1], my[1], mz[1], 0, mx[2], my[2], mz[2], 0, tx, ty, tz, 1])
def scale(s):
return [s,0,0,0, 0,s,0,0, 0,0,s,0, 0,0,0,1]
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 (delete_shaders):
for i in range(0, shader_count):
glDeleteShader(shaders[i]);
return program
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)
return result
def load_shaders():
global clear_program
global append_program
global resolve_program
global uniform
shaders = [GLuint(0), GLuint(0)]
shaders[0] = shader_load("fragmentlist_shaders/clear.vs.glsl", GL_VERTEX_SHADER);
shaders[1] = shader_load("fragmentlist_shaders/clear.fs.glsl", GL_FRAGMENT_SHADER);
if (clear_program):
glDeleteProgram(clear_program);
clear_program = link_from_shaders(shaders, 2, True);
shaders[0] = shader_load("fragmentlist_shaders/append.vs.glsl", GL_VERTEX_SHADER);
shaders[1] = shader_load("fragmentlist_shaders/append.fs.glsl", GL_FRAGMENT_SHADER);
if (append_program):
glDeleteProgram(append_program);
append_program = link_from_shaders(shaders, 2, True);
uniform.mvp = glGetUniformLocation(append_program, "mvp");
shaders[0] = shader_load("fragmentlist_shaders/resolve.vs.glsl", GL_VERTEX_SHADER);
shaders[1] = shader_load("fragmentlist_shaders/resolve.fs.glsl", GL_FRAGMENT_SHADER);
if (resolve_program):
glDeleteProgram(resolve_program)
resolve_program = link_from_shaders(shaders, 2, True);
class Scene:
def __init__(self, width, height):
global uniforms_buffer
global fragment_buffer
global atomic_counter_buffer
global head_pointer_image
global dummy_vao
global myobject
self.width = width
self.height = height
load_shaders()
glGenBuffers(1, uniforms_buffer)
glBindBuffer(GL_UNIFORM_BUFFER, uniforms_buffer)
glBufferData(GL_UNIFORM_BUFFER, sizeof(GLfloat * 16 *3), None, GL_DYNAMIC_DRAW)
myobject.load("dragon.sbm")
glGenBuffers(1, fragment_buffer)
glBindBuffer(GL_SHADER_STORAGE_BUFFER, fragment_buffer);
glBufferData(GL_SHADER_STORAGE_BUFFER, 1024 * 1024 * 16, None, GL_DYNAMIC_COPY)
glGenBuffers(1, atomic_counter_buffer);
glBindBuffer(GL_ATOMIC_COUNTER_BUFFER, atomic_counter_buffer);
glBufferData(GL_ATOMIC_COUNTER_BUFFER, 4, None, GL_DYNAMIC_COPY);
head_pointer_image = glGenTextures(1)
glBindTexture(GL_TEXTURE_2D, head_pointer_image);
glTexStorage2D(GL_TEXTURE_2D, 1, GL_R32UI, 1024, 1024);
glGenVertexArrays(1, dummy_vao);
glBindVertexArray(dummy_vao);
def display(self):
green = [ 0.0, 0.1, 0.0, 0.0 ]
currentTime = time.time()
f = currentTime
zeros = [ 0.0, 0.0, 0.0, 0.0 ]
gray = [ 0.1, 0.1, 0.1, 0.0 ]
ones = [ 1.0 ]
glViewport(0, 0, self.width , self.height);
glMemoryBarrier(GL_SHADER_IMAGE_ACCESS_BARRIER_BIT | GL_ATOMIC_COUNTER_BARRIER_BIT | GL_SHADER_STORAGE_BARRIER_BIT);
glUseProgram(clear_program);
glBindVertexArray(dummy_vao);
glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
glUseProgram(append_program)
model_matrix = (GLfloat * 16)(*identityMatrix)
model_matrix = scale(6.0)
view_matrix = (GLfloat * 16)(*identityMatrix)
view_matrix = m3dLookAt([math.cos(f * 0.35) * 120.0, math.cos(f * 0.4) * 30.0, math.sin(f * 0.35) * 120.0],
[0.0, -20.0, 0.0],
[0.0, 1, 0.0])
mv_matrix = (GLfloat * 16)(*identityMatrix)
mv_matrix = m3dMultiply(view_matrix , model_matrix)
proj_matrix = (GLfloat * 16)(*identityMatrix)
proj_matrix = m3dPerspective(m3dDegToRad(50.0), float(self.width) / float(self.height), 0.1, 1000.0)
glUniformMatrix4fv(uniform.mvp, 1, GL_FALSE, m3dMultiply(proj_matrix , mv_matrix))
zero = 0;
glBindBufferBase(GL_ATOMIC_COUNTER_BUFFER, 0, atomic_counter_buffer)
# next line not working ????
#glBufferSubData(GL_ATOMIC_COUNTER_BUFFER, 0, sys.getsizeof(zero), zero);
glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 0, fragment_buffer)
glBindImageTexture(0, head_pointer_image, 0, GL_FALSE, 0, GL_READ_WRITE, GL_R32UI)
glMemoryBarrier(GL_SHADER_IMAGE_ACCESS_BARRIER_BIT | GL_ATOMIC_COUNTER_BARRIER_BIT | GL_SHADER_STORAGE_BARRIER_BIT)
myobject.render()
glMemoryBarrier(GL_SHADER_IMAGE_ACCESS_BARRIER_BIT | GL_ATOMIC_COUNTER_BARRIER_BIT | GL_SHADER_STORAGE_BARRIER_BIT)
glUseProgram(resolve_program)
glBindVertexArray(dummy_vao)
glMemoryBarrier(GL_SHADER_IMAGE_ACCESS_BARRIER_BIT | GL_ATOMIC_COUNTER_BARRIER_BIT | GL_SHADER_STORAGE_BARRIER_BIT)
glDrawArrays(GL_TRIANGLE_STRIP, 0, 4)
glutSwapBuffers()
def reshape(self, width, height):
self.width = width
self.height = height
def keyboard(self, key, x, y ):
global fullscreen
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
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 - Fragment List')
glutInitWindowPosition(int((1360/2)-(512/2)), int((768/2)-(512/2)))
fullscreen = False
many_cubes = 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()
The output is supposed to appear like the following:
Ported from fragmentlist.cpp found there from the Superbible OpenGL 7th ed.
Current Question:
Any ideas why the texture rendered on the dragon is not translucent as the expected output has it?
The view space is the local system which is defined by the point of view onto the scene.
The position of the view, the line of sight and the upwards direction of the view, define a coordinate system relative to the world coordinate system. The objects of a scene have to be drawn in relation to the view coordinate system, to be "seen" from the viewing position. The inverse matrix of the view coordinate system is named the view matrix. This matrix transforms from world coordinates to view coordinates.
The code below defines a matrix that exactly encapsulates the steps necessary to calculate a look at the scene:
Converting model coordinates into view system coordinates.
Rotation, to look in the direction of the view.
Movement to the eye position.
Euclidean length of a vector:
def length(v):
return math.sqrt(v[0]*v[0]+v[1]*v[1]+v[2]*v[2])
Unit vector:
def normalize(v):
l = length(v)
return [v[0]/l, v[1]/l, v[2]/l]
Dot product:
def dot(v0, v1):
return v0[0]*v1[0]+v0[1]*v1[1]+v0[2]*v1[2]
Cross product:
def cross(v0, v1):
return [
v0[1]*v1[2]-v1[1]*v0[2],
v0[2]*v1[0]-v1[2]*v0[0],
v0[0]*v1[1]-v1[0]*v0[1]]
The following code does the same as gluLookAt or glm::lookAt does:
The parameter eye is the point of view, target is the point which is looked at and up is the upwards direction.
def m3dLookAt(eye, target, up):
mz = normalize( (eye[0]-target[0], eye[1]-target[1], eye[2]-target[2]) ) # inverse line of sight
mx = normalize( cross( up, mz ) )
my = normalize( cross( mz, mx ) )
tx = dot( mx, eye )
ty = dot( my, eye )
tz = -dot( mz, eye )
return np.array([mx[0], my[0], mz[0], 0, mx[1], my[1], mz[1], 0, mx[2], my[2], mz[2], 0, tx, ty, tz, 1])
Use it like this:
view_matrix = m3dLookAt([0, 0, 20], [0, 0, 0], [0, 1, 0])
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.