Related
Let's start by considering this simple snippet:
import ctypes
import textwrap
import time
import glfw
import numpy as np
from OpenGL.GL import *
from OpenGL.GLU import *
from OpenGL.GLUT import *
import glm
GLSL_VERSION = "#version 440\n"
CONTEXT_VERSION = (4, 1)
def vs_shader(text):
return GLSL_VERSION + textwrap.dedent(text)
def shader(text):
prefix = textwrap.dedent("""\
uniform float iTime;
uniform int iFrame;
uniform vec3 iResolution;
uniform sampler2D iChannel0;
uniform vec2 iOffset;
out vec4 frag_color;
""")
suffix = textwrap.dedent("""\
void main() {
mainImage(frag_color, gl_FragCoord.xy + iOffset);
}
""")
return GLSL_VERSION + prefix + textwrap.dedent(text) + suffix
VS = vs_shader("""\
layout(location = 0) in vec3 in_position;
uniform mat4 mvp;
void main()
{
gl_Position = mvp * vec4(in_position, 1.0f);
}
""")
SIMPLE = [
shader("""
void mainImage( out vec4 fragColor, in vec2 fragCoord )
{
vec2 uv = fragCoord.xy / iResolution.xy;
float tile_size = 4;
vec2 g = floor(vec2(tile_size, tile_size) * uv);
float c = mod(g.x + g.y, 2.0);
if (uv.x<0.5 && uv.y<0.5)
fragColor = vec4(mix(vec3(c), vec3(1), vec3(1,0,1)), 1.0);
else if (uv.x>=0.5 && uv.y<0.5)
fragColor = vec4(mix(vec3(c), vec3(1), vec3(1,0,0)), 1.0);
else if (uv.x<0.5 && uv.y>=0.5)
fragColor = vec4(mix(vec3(c), vec3(1), vec3(0,1,0)), 1.0);
else if (uv.x>=0.5 && uv.y>=0.5)
fragColor = vec4(mix(vec3(c), vec3(1), vec3(0,0,1)), 1.0);
}
"""),
shader("""
void mainImage( out vec4 fragColor, in vec2 fragCoord )
{
vec2 uv = fragCoord/iResolution.xy;
fragColor = vec4(texture(iChannel0, uv).rgb,1.0);
}
""")
]
# -------- MINIFRAMEWORK --------
class Tiler:
def __init__(self, scene_width, scene_height):
self.scene_width = scene_width
self.scene_height = scene_height
#classmethod
def from_num_tiles(cls, scene_width, scene_height, num_tiles_x, num_tiles_y):
obj = cls(scene_width, scene_height)
obj.num_tiles_x = num_tiles_x
obj.num_tiles_y = num_tiles_y
obj.tile_width = obj.scene_width // num_tiles_x
obj.tile_height = obj.scene_height // num_tiles_y
return obj
#classmethod
def from_size(cls, scene_width, scene_height, tile_width, tile_height):
obj = cls(scene_width, scene_height)
obj.num_tiles_x = obj.scene_width // tile_width
obj.num_tiles_y = obj.scene_height // tile_height
obj.tile_width = tile_width
obj.tile_height = tile_height
return obj
#property
def num_tiles(self):
return self.num_tiles_y * self.num_tiles_x
class TextureF32():
def __init__(self, width, height):
target = GL_TEXTURE_2D
self.target = target
self.identifier = glGenTextures(1)
glPixelStorei(GL_UNPACK_ALIGNMENT, 1)
glBindTexture(target, self.identifier)
glTexImage2D(target, 0, GL_RGBA32F, width, height, 0, GL_RGBA, GL_FLOAT, None)
glTexParameteri(target, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE)
glTexParameteri(target, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE)
self.set_filter()
glBindTexture(target, 0)
def set_filter(self):
glTexParameteri(self.target, GL_TEXTURE_MAG_FILTER, GL_NEAREST)
glTexParameteri(self.target, GL_TEXTURE_MIN_FILTER, GL_NEAREST)
def bind(self):
glBindTexture(self.target, self.identifier)
def unbind(self):
glBindTexture(self.target, 0)
class FboF32():
def __init__(self, width, height):
self.target = GL_FRAMEBUFFER
self.identifier = glGenFramebuffers(1)
glBindFramebuffer(GL_FRAMEBUFFER, self.identifier)
# Color attachments
tex = TextureF32(width, height)
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, tex.identifier, 0)
glDrawBuffers(1, [GL_COLOR_ATTACHMENT0])
self.colors = [tex]
self.width = width
self.height = height
if glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE:
raise Exception(
f"ERROR::FRAMEBUFFER:: Framebuffer {self.identifier} is not complete!"
)
glBindFramebuffer(GL_FRAMEBUFFER, 0)
def delete(self):
self.glDeleteFramebuffers(self.identifier)
def rect(self):
return [0, 0, self.width, self.height]
def bind(self):
glBindFramebuffer(GL_FRAMEBUFFER, self.identifier)
def set_uniform1f(prog, name, v0):
glUniform1f(glGetUniformLocation(prog, name), v0)
def set_uniform1i(prog, name, v0):
glUniform1i(glGetUniformLocation(prog, name), v0)
def set_uniform2i(prog, name, v0, v1):
glUniform2i(glGetUniformLocation(prog, name), v0, v1)
def set_uniform2f(prog, name, v0, v1):
glUniform2f(glGetUniformLocation(prog, name), v0, v1)
def set_uniform3f(prog, name, v0, v1, v2):
glUniform3f(glGetUniformLocation(prog, name), v0, v1, v2)
def set_uniform_mat4(prog, name, mat):
glUniformMatrix4fv(glGetUniformLocation(prog, name), 1, GL_FALSE, glm.value_ptr(mat))
def set_uniform_texture(prog, name, resource, unit_texture):
glActiveTexture(GL_TEXTURE0 + unit_texture)
resource.bind()
resource.set_filter()
glUniform1i(glGetUniformLocation(prog, name), 0 + unit_texture)
def create_quad(x0, y0, x1, y1):
data = np.array([
x0, y0, 0,
x1, y0, 0,
x0, y1, 0,
x1, y0, 0,
x1, y1, 0,
x0, y1, 0,
], dtype=np.float32)
vbo = glGenBuffers(1)
glBindBuffer(GL_ARRAY_BUFFER, vbo)
glBufferData(GL_ARRAY_BUFFER, data, GL_STATIC_DRAW)
vao = glGenVertexArrays(1)
glBindVertexArray(vao)
glVertexAttribPointer(0, 3, GL_FLOAT, False, 0, ctypes.c_void_p(0))
glEnableVertexAttribArray(0)
return vao
def compile(shader_type, source):
identifier = glCreateShader(shader_type)
glShaderSource(identifier, source)
glCompileShader(identifier)
if not glGetShaderiv(identifier, GL_COMPILE_STATUS):
for i, l in enumerate(source.splitlines()):
print(f"{i+1}: {l}")
raise Exception(glGetShaderInfoLog(identifier).decode("utf-8"))
return identifier
def create_program(vs, fs):
vs_identifier = compile(GL_VERTEX_SHADER, vs)
fs_identifier = compile(GL_FRAGMENT_SHADER, fs)
program = glCreateProgram()
glAttachShader(program, vs_identifier)
glAttachShader(program, fs_identifier)
glLinkProgram(program)
if not glGetProgramiv(program, GL_LINK_STATUS):
raise RuntimeError(glGetProgramInfoLog(program))
return program
# -------- Glut/Glfw --------
class Effect:
def __init__(self, w, h, num_tiles_x, num_tiles_y, passes):
self.fbos = []
self.needs_updating = True
self.allocations = 0
self.tiler = Tiler.from_num_tiles(w, h, num_tiles_x, num_tiles_y)
self.passes = [create_program(VS, rp) for rp in passes]
self.iframe = 0
self.start_time = time.time()
self.quad = create_quad(-1, -1, 1, 1)
self.view = glm.lookAt(
glm.vec3(0, 0, 10),
glm.vec3(0, 0, 0),
glm.vec3(0, 1, 0)
)
self.model = glm.mat4(1)
glEnable(GL_DEPTH_TEST)
# print("GL_MAX_VIEWPORT_DIMS:", glGetIntegerv(GL_MAX_VIEWPORT_DIMS))
# print("GL_MAX_TEXTURE_SIZE:", glGetIntegerv(GL_MAX_TEXTURE_SIZE))
# print("GL_MAX_RENDERBUFFER_SIZE:", glGetIntegerv(GL_MAX_RENDERBUFFER_SIZE))
def mem_info(self):
GL_GPU_MEM_INFO_TOTAL_AVAILABLE_MEM_NVX = 0x9048
GL_GPU_MEM_INFO_CURRENT_AVAILABLE_MEM_NVX = 0x9049
total_mem_kb = glGetIntegerv(GL_GPU_MEM_INFO_TOTAL_AVAILABLE_MEM_NVX)
cur_avail_mem_kb = glGetIntegerv(GL_GPU_MEM_INFO_CURRENT_AVAILABLE_MEM_NVX)
return f"total_mem_kb={total_mem_kb} cur_avail_mem_kb={cur_avail_mem_kb}"
def create_fbo(self, tiler):
return [
FboF32(width=tiler.tile_width, height=tiler.tile_height)
for i in range(tiler.num_tiles)
]
def make_ortho(self, x, y, num_tiles_x, num_tiles_y, left, right, bottom, top, near, far):
# References
#
# https://www.opengl.org/archives/resources/code/samples/advanced/advanced97/notes/node20.html
# https://stackoverflow.com/questions/6490728/capture-snapshot-of-opengl-window-with-very-large-image-resolution
#
offset_x = (right - left) / num_tiles_x
offset_y = (top - bottom) / num_tiles_y
l = left + offset_x * x
r = left + offset_x * (x + 1)
b = bottom + offset_y * y
t = bottom + offset_y * (y + 1)
n = near
f = far
print(f"x={x} y={y} left={l} right={r} bottom={b} top={t}")
return glm.ortho(l, r, b, t, n, f)
def render_pass(self, rp, mvp, w, h, channel0, offset_x=0, offset_y=0):
t = time.time() - self.start_time
glBindVertexArray(self.quad)
glUseProgram(rp)
set_uniform_mat4(rp, "mvp", mvp)
set_uniform1f(rp, "iTime", t)
set_uniform1i(rp, "iFrame", self.iframe)
set_uniform3f(rp, "iResolution", w, h, w / h)
set_uniform2f(rp, "iOffset", offset_x, offset_y)
if channel0:
set_uniform_texture(rp, "iChannel0", channel0, self.active_texture)
self.active_texture += 1
glDrawArrays(GL_TRIANGLES, 0, 6)
# No tile rendering
def render_no_tiles(self, window_width, window_height):
self.active_texture = 0
if self.needs_updating:
if not self.fbos:
print(f"Creating fbos, allocations={self.allocations} {self.mem_info()}")
self.fbos = [
FboF32(width=window_width, height=window_height),
FboF32(width=window_width, height=window_height)
]
# clear buffers
if self.iframe == 0:
for fbo in self.fbos:
fbo.bind()
glViewport(*fbo.rect())
glClearColor(0, 0, 0, 0)
glClear(GL_COLOR_BUFFER_BIT)
proj = glm.ortho(-1, 1, -1, 1, -100, 100)
mvp = proj * self.view * self.model
# Pass0: BufferA - Channels [BufferA, None, None, None]
fbo0 = self.fbos[0]
fbo1 = self.fbos[1]
w, h = fbo0.width, fbo0.height
rp = self.passes[0]
fbo0.bind()
glViewport(0, 0, w, h)
self.render_pass(rp, mvp, w, h, fbo1.colors[0])
# Pass1: Image - Channels [BufferA, None, None, None]
glBindFramebuffer(GL_FRAMEBUFFER, 0)
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)
fbo0 = self.fbos[0]
w, h = window_width, window_height
rp = self.passes[1]
glViewport(0, 0, w, h)
self.render_pass(rp, mvp, w, h, fbo0.colors[0])
# ping-pong
self.fbos.reverse()
self.iframe += 1
# Tile rendering
def render_tiles(self, window_width, window_height):
M = self.tiler.num_tiles_x
N = self.tiler.num_tiles_y
offset_x = window_width // M
offset_y = window_height // N
proj = glm.ortho(-1, 1, -1, 1, -100, 100)
# -------- Test --------
# glBindFramebuffer(GL_FRAMEBUFFER, 0)
# glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)
# self.active_texture = 0
# for y in range(N):
# for x in range(M):
# w, h = window_width, window_height
# mvp = proj * self.view * self.model
# glViewport(offset_x * x, offset_y * y, self.tiler.tile_width, self.tiler.tile_height)
# self.render_pass(self.passes[0], mvp, w, h, None, offset_x * x, offset_y * y)
# return
# -------- Test2 --------
self.active_texture = 0
if self.needs_updating:
if not self.fbos:
print(f"Creating fbos, allocations={self.allocations} {self.mem_info()}")
self.fbos = [
self.create_fbo(self.tiler),
self.create_fbo(self.tiler),
]
# clear buffers
if self.iframe == 0:
for fbo_tiles in self.fbos:
for fbo in fbo_tiles:
fbo.bind()
glViewport(*fbo.rect())
glClearColor(0, 0, 0, 0)
glClear(GL_COLOR_BUFFER_BIT)
# Pass0: BufferA - Channels [BufferA, None, None, None]
for y in range(N):
for x in range(M):
fbo0 = self.fbos[0][y * M + x]
fbo1 = self.fbos[1][y * M + x]
w, h, aspect = fbo0.width, fbo0.height, fbo0.width / fbo0.height
mvp = proj * self.view * self.model
rp = self.passes[0]
fbo0.bind()
glViewport(0, 0, self.tiler.tile_width, self.tiler.tile_height)
self.render_pass(rp, mvp, w, h, fbo1.colors[0], offset_x * x, offset_y * y)
# Pass1: Image - Channels [BufferA, None, None, None]
glBindFramebuffer(GL_FRAMEBUFFER, 0)
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)
for y in range(N):
for x in range(M):
fbo0 = self.fbos[0][y * M + x]
fbo1 = self.fbos[1][y * M + x]
w, h, aspect = window_width, window_height, window_width / window_height
mvp = proj * self.view * self.model
rp = self.passes[1]
glViewport(offset_x * x, offset_y * y, self.tiler.tile_width, self.tiler.tile_height)
self.render_pass(rp, mvp, w, h, fbo0.colors[0], 0, 0)
# ping-pong
self.fbos.reverse()
self.iframe += 1
class WindowGlut:
def __init__(self, w, h, use_tiles, num_tiles_x, num_tiles_y, passes):
glutInit()
glutInitContextVersion(*CONTEXT_VERSION)
glutInitContextProfile(GLUT_CORE_PROFILE)
glutInitContextFlags(GLUT_FORWARD_COMPATIBLE)
glutSetOption(GLUT_MULTISAMPLE, 16)
glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGB | GLUT_DEPTH | GLUT_MULTISAMPLE)
glutInitWindowSize(w, h)
glutCreateWindow('Mcve')
glutReshapeFunc(self.reshape)
glutKeyboardFunc(self.keyboard_func)
glutKeyboardUpFunc(self.keyboard_up_func)
glutDisplayFunc(self.display)
glutIdleFunc(self.idle_func)
self.keys = {chr(i): False for i in range(256)}
self.effect = Effect(w, h, num_tiles_x, num_tiles_y, passes)
self.start_time = time.time()
self.num_frames = 0
if use_tiles:
print("TILE RENDERING ENABLED")
self.render = self.effect.render_tiles
else:
print("TILE RENDERING DISABLED")
self.render = self.effect.render_no_tiles
def keyboard_func(self, *args):
self.keys[args[0].decode("utf8")] = True
def keyboard_up_func(self, *args):
self.keys[args[0].decode("utf8")] = False
def display(self):
if self.keys['r']:
self.effect.iframe = 0
self.render(self.window_width, self.window_height)
glutSwapBuffers()
self.num_frames += 1
t = time.time() - self.start_time
if t >= 1:
glutSetWindowTitle(f"Fps: {self.num_frames}")
self.start_time = time.time()
self.num_frames = 0
def run(self):
glutMainLoop()
def idle_func(self):
glutPostRedisplay()
def reshape(self, w, h):
glViewport(0, 0, w, h)
self.window_width = w
self.window_height = h
class WindowGlfw:
def __init__(self, w, h, use_tiles, num_tiles_x, num_tiles_y, passes):
# Initialize the library
if not glfw.init():
return
# Create a windowed mode window and its OpenGL context
glfw.window_hint(glfw.CONTEXT_VERSION_MAJOR, CONTEXT_VERSION[0])
glfw.window_hint(glfw.CONTEXT_VERSION_MINOR, CONTEXT_VERSION[1])
glfw.window_hint(glfw.OPENGL_FORWARD_COMPAT, GL_TRUE)
glfw.window_hint(glfw.OPENGL_PROFILE, glfw.OPENGL_CORE_PROFILE)
window = glfw.create_window(w, h, "Mcve", None, None)
if not window:
glfw.terminate()
return
glfw.set_window_size_callback(window, self.reshape)
glfw.set_key_callback(window, self.keyboard_func)
# Make the window's context current
glfw.make_context_current(window)
self.window = window
self.keys = {chr(i): False for i in range(256)}
self.effect = Effect(w, h, num_tiles_x, num_tiles_y, passes)
self.window_width = w
self.window_height = h
if use_tiles:
print("TILE RENDERING ENABLED")
self.render = self.effect.render_tiles
else:
print("TILE RENDERING DISABLED")
self.render = self.effect.render_no_tiles
def keyboard_func(self, window, key, scancode, action, mods):
self.keys[chr(key)] = action
def display(self):
if self.keys['R']:
self.iframe = 0
self.render(self.window_width, self.window_height)
def run(self):
window = self.window
while not glfw.window_should_close(window):
self.display()
glfw.swap_buffers(window)
glfw.poll_events()
glfw.terminate()
def reshape(self, window, w, h):
glViewport(0, 0, w, h)
self.window_width = w
self.window_height = h
if __name__ == '__main__':
params = {
"w": 320,
"h": 240,
"use_tiles": True,
"num_tiles_x": 2,
"num_tiles_y": 2,
"passes": SIMPLE
}
use_glut = True
WindowGlut(**params).run() if use_glut else WindowGlfw(**params).run()
To run this code you'll need to install numpy, pyopengl, glfw, PyGLM. You can switch between glfw or glut by toggling the variable use_glut. I've added this options as it seems running glut on macosx may be tricky in certain cases.
Anyway, the goal of this thread is to figure out how to fix the buggy snippet to make proper tile rendering, as you can see right now there is a very naive attempt implemented.
In the main block you can specify if you want to use a render method using tiles or not (use_tiles variable), if you choose using tiles you'll need to specify the number of them (num_tiles_x, num_tiles_y).
Cases:
If you run it with "use_tiles": False you'll see this output:
that output is correct
If you run it with "use_tiles": True, "num_tiles_x": 2, "num_tiles_y": 2 you should see the same output than 1). Also correct
But if you run it with "use_tiles": True, "num_tiles_x": 4, "num_tiles_y": 4 or higher you'll start seeing a totally screwed up image like below:
QUESTION: What's the bug of my tile rendering code that's producing the wrong output? How would you fix it?
Also... Even if the code is fixed the way I'm trying to make tile rendering is quite naive and it won't work very well when dealing with more complex effects where passes need to read back from adjacent tiles or even worst, non-adjacent tiles. For the case of adjacent tiles I've been told adding some padding to the tiles would work pretty well but for the more general case i don't have a clue how'd you tackle that problem. In any case, one step at a time, the goal of this thread would be fixing the buggy snippet
In the first pass a single tile is rendered to a framebuffer, which has exactly the size of the tile. gl_FragCoord.xy is (0,0) at the bottom left of the tile. uv = (0,0) has to be at the bottom left of the window and uv = (1, 1) at the top right of the window. To calculate the the uv coordinate in respect to the window, you've to add the offset of the tile to gl_FragCoord.xy and to divide by the size of the window:
formula (pseudo code):
uv = (gl_FragCoord.xy + (offset_x*x, offset_y*y)) / (window_width, window_height)
+------------------+
| |
| +----+ |
| | | |
| +----+ |
| (0,0) tile = gl_FragCoord.xy
| |
+------------------+
(0,0) window
In the first pass, iResolution has to be (window_width, window_height) and iOffset has to be (offset_x * x, offset_y * y).
# Pass0: BufferA - Channels [BufferA, None, None, None]
for y in range(N):
for x in range(M):
fbo0 = self.fbos[0][y * M + x]
fbo1 = self.fbos[1][y * M + x]
mvp = proj * self.view * self.model
rp = self.passes[0]
fbo0.bind()
glViewport(0, 0, self.tiler.tile_width, self.tiler.tile_height)
w, h = window_width, window_height
aspect = window_width / window_height
self.render_pass(rp, mvp, w, h, fbo1.colors[0], offset_x * x, offset_y * y)
In the second pass a single tile is read form the texture and renderd to the window (default framebuffer 0). The source texture (tile) has exactly the size of a tile and the uv coordinate has to be calculated in respect to the tile texture. gl_FragCoord.xy is (0,0) at the bottom left of the window. uv = (0,0) has to be at the bottom left of the tile and uv = (1, 1) at the top right of the tile. To calculate the uv coordinate the offset of the tile has to subtracted from gl_FragCoord.xy and the result has to be divided by the size of a title :
formula (pseudo code)
uv = (gl_FragCoord.xy - (offset_x*x, offset_y*y)) / (tile_width, tile_height)
+------------------+
| |
| +----+ |
| | | |
| +----+ |
| (0,0) tile |
| |
+------------------+
(0,0) window = gl_FragCoord.xy
In the 2nd pass, iResolution has to be (self.tiler.tile_width, self.tiler.tile_height) and iOffset has to be (-offset_x * x, -offset_y * y).
# Pass1: Image - Channels [BufferA, None, None, None]
glBindFramebuffer(GL_FRAMEBUFFER, 0)
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)
for y in range(N):
for x in range(M):
fbo0 = self.fbos[0][y * M + x]
fbo1 = self.fbos[1][y * M + x]
mvp = proj * self.view * self.model
rp = self.passes[1]
glViewport(offset_x*x, offset_y*y, self.tiler.tile_width, self.tiler.tile_height)
w, h = self.tiler.tile_width, self.tiler.tile_height
aspect = self.tiler.tile_width / self.tiler.tile_height
self.render_pass(rp, mvp, w, h, fbo0.colors[0], -offset_x * x, -offset_y * y)
Edit for mcve.py
In this case the render target is always a framebuffer with the size of a tile The 2nd render pass ("Pass1") reads from a tile and stores to the destination tile, so the 2nd pass has to be:
# Pass1: Image - Channels [BufferA, None, None, None]
for y in range(N):
for x in range(M):
fbo_dst = self.fbo_target[0][y * M + x]
fbo_src = self.fbos[0][y * M + x]
mvp = proj * self.view * self.model
rp = self.passes[1]
fbo_dst.bind()
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)
glViewport(0, 0, self.tiler.tile_width, self.tiler.tile_height)
w, h = self.tiler.tile_width, self.tiler.tile_height
aspect = self.tiler.tile_width / self.tiler.tile_height
self.render_pass(rp, mvp, w, h, fbo_src.colors[0], 0, 0)
A further issue is the reading of the texture form the previous frame in the fragments shader. The size of the texture is always a the size of a tile. The bottom left coordinate of the texture is (0, 0) and the top right coordinate is (1, 1).
So for the calculation of the texture coordinate (st), the offset has to be skipped and the resolution is given by the size of the texture (textureSize):
void mainImage( out vec4 fragColor, in vec2 fragCoord ) {
initSpheres();
# issue is here
// vec2 st = fragCoord.xy / iResolution.xy; <--- delete
vec2 st = gl_FragCoord.xy / vec2(textureSize(iChannel0, 0));
// [...]
// Moving average (multipass code)
vec3 color = texture(iChannel0, st).rgb * float(iFrame);
// [...]
}
See the result:
If you don't want to change the shader code in mainImage, then a different approach is to trick the system and to delegate the texture look up to a different function, by a macro. e.g.:
def shader(tileTextureLookup, text):
prefix = textwrap.dedent("""\
uniform float iTime;
uniform int iFrame;
uniform vec3 iResolution;
uniform sampler2D iChannel0;
uniform vec2 iOffset;
out vec4 frag_color;
""")
textureLookup = ""
if tileTextureLookup:
textureLookup = textwrap.dedent("""\
vec4 textureTile(sampler2D sampler, vec2 uv) {
vec2 st = (uv * iResolution.xy - iOffset.xy) / vec2(textureSize(sampler, 0));
return texture(sampler, st);
}
#define texture textureTile
""")
suffix = textwrap.dedent("""\
void main() {
mainImage(frag_color, gl_FragCoord.xy + iOffset);
}
""")
return GLSL_VERSION + prefix + textureLookup + textwrap.dedent(text) + suffix
SMALLPT_MULTIPASS = [
shader(True, """\
// All code here is by Zavie (https://www.shadertoy.com/view/4sfGDB#)
// [...]
"""),
shader(False, """\
// A simple port of Zavie's GLSL smallpt that uses multipass.
// Original source: https://www.shadertoy.com/view/4sfGDB#
void mainImage( out vec4 fragColor, in vec2 fragCoord ) {
vec2 uv = fragCoord.xy / iResolution.xy;
vec3 color = texture(iChannel0, uv).rgb;
fragColor = vec4(pow(clamp(color, 0., 1.), vec3(1./2.2)), 1.);
}
""")
]
But note, texture is an overloaded function and this approach works for 2 dimensional textures only. Furthermore there other look up functions like texelFetch, too.
How to resolve an error that I'm getting trying to assign values to memory allocated by glMapBufferRange This is a problematic function, due to not having casts in python. I am trying to assign values to this memory, though am experiencing the error stated in the title. I have also tried to create a np.array with a dimension of the size material but to no avail. Perhaps just not doing it correctly.
Update and SUCCESS! With the amazing help of Rabbid76 and the fixes first to allocate and be able to assign to glMapBufferRange memory, and then to the sbmloader.py the program renders successfully. Thank you.
Final results also found on my github PythonOpenGLSuperBible7Glut
support files: hdrbloom_support.zip
expected output rendering is what the actual results are rendering:
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_()
MAX_SCENE_WIDTH = 2048
MAX_SCENE_HEIGHT = 2048
SPHERE_COUNT = 32
tex_src = GLuint(0)
tex_lut = GLuint(0)
render_fbo = GLuint(0)
filter_fbo = [ GLuint(0) for _ in range(2) ]
tex_scene = GLuint(0)
tex_brightpass = GLuint(0)
tex_depth = GLuint(0)
tex_filter = [ GLuint(0) for _ in range(2) ]
program_render = GLuint(0)
program_filter = GLuint(0)
program_resolve = GLuint(0)
vao = GLuint(0)
exposure = 1.0
mode = 0
paused = False
bloom_factor = 1.0
show_bloom = True
show_scene = True
show_prefilter = False
bloom_thresh_min = 0.8
bloom_thresh_max = 1.2
class UNIFORMS_:
class scene:
bloom_thresh_min = 0.8
bloom_thresh_max = 1.2
class resolve:
exposure = 1.0
bloom_factor = 1.0
scene_factor = 0
uniforms = UNIFORMS_()
ubo_transform = GLuint(0)
ubo_material = GLuint(0)
def load_shaders():
global program_render
global program_filter
global program_resolve
global uniforms
shaders = [GLuint(0), GLuint(0)]
if (program_render):
glDeleteProgram(program_render)
shaders[0] = shader_load("hdrbloom-scene.vs.glsl", GL_VERTEX_SHADER)
shaders[1] = shader_load("hdrbloom-scene.fs.glsl", GL_FRAGMENT_SHADER)
program_render = link_from_shaders(shaders, 2, True)
uniforms.scene.bloom_thresh_min = glGetUniformLocation(program_render, "bloom_thresh_min")
uniforms.scene.bloom_thresh_max = glGetUniformLocation(program_render, "bloom_thresh_max")
if (program_filter):
glDeleteProgram(program_filter)
shaders[0] = shader_load("hdrbloom-filter.vs.glsl", GL_VERTEX_SHADER)
shaders[1] = shader_load("hdrbloom-filter.fs.glsl", GL_FRAGMENT_SHADER)
program_filter = link_from_shaders(shaders, 2, True)
if (program_resolve):
glDeleteProgram(program_resolve)
shaders[0] = shader_load("hdrbloom-resolve.vs.glsl", GL_VERTEX_SHADER)
shaders[1] = shader_load("hdrbloom-resolve.fs.glsl", GL_FRAGMENT_SHADER)
program_resolve = link_from_shaders(shaders, 2, True)
uniforms.resolve.exposure = glGetUniformLocation(program_resolve, "exposure")
uniforms.resolve.bloom_factor = glGetUniformLocation(program_resolve, "bloom_factor")
uniforms.resolve.scene_factor = glGetUniformLocation(program_resolve, "scene_factor")
class Scene:
def __init__(self, width, height):
global myobject
global vao
global render_fbo
global tex_scene
global tex_brightpass
global tex_depth
global filter_fbo
global tex_filter
global tex_lut
global ubo_transform
global ubo_material
self.width = width
self.height = height
buffers = [ GL_COLOR_ATTACHMENT0, GL_COLOR_ATTACHMENT1 ]
glGenVertexArrays(1, vao)
glBindVertexArray(vao)
load_shaders()
exposureLUT = [ 11.0, 6.0, 3.2, 2.8, 2.2, 1.90, 1.80, 1.80, 1.70, 1.70, 1.60, 1.60, 1.50, 1.50, 1.40, 1.40, 1.30, 1.20, 1.10, 1.00 ]
glGenFramebuffers(1, render_fbo)
glBindFramebuffer(GL_FRAMEBUFFER, render_fbo)
tex_scene = glGenTextures(1)
glBindTexture(GL_TEXTURE_2D, tex_scene)
glTexStorage2D(GL_TEXTURE_2D, 1, GL_RGBA16F, MAX_SCENE_WIDTH, MAX_SCENE_HEIGHT)
glFramebufferTexture(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, tex_scene, 0)
tex_brightpass = glGenTextures(1)
glBindTexture(GL_TEXTURE_2D, tex_brightpass)
glTexStorage2D(GL_TEXTURE_2D, 1, GL_RGBA16F, MAX_SCENE_WIDTH, MAX_SCENE_HEIGHT)
glFramebufferTexture(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT1, tex_brightpass, 0)
tex_depth = glGenTextures(1)
glBindTexture(GL_TEXTURE_2D, tex_depth)
glTexStorage2D(GL_TEXTURE_2D, 1, GL_DEPTH_COMPONENT32F, MAX_SCENE_WIDTH, MAX_SCENE_HEIGHT)
glFramebufferTexture(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, tex_depth, 0)
glDrawBuffers(2, buffers)
#glGenFramebuffers(2, filter_fbo[0])
filter_fbo = [ glGenFramebuffers(1) for _ in range(2)]
#glGenTextures(2, tex_filter[0])
tex_filter = [glGenTextures(1) for _ in range(2)]
for i in range(0,2):
glBindFramebuffer(GL_FRAMEBUFFER, filter_fbo[i])
glBindTexture(GL_TEXTURE_2D, tex_filter[i])
glTexStorage2D(GL_TEXTURE_2D, 1, GL_RGBA16F, MAX_SCENE_WIDTH if i==0 else MAX_SCENE_HEIGHT, MAX_SCENE_HEIGHT if i==0 else MAX_SCENE_WIDTH)
glFramebufferTexture(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, tex_filter[i], 0)
glDrawBuffers(1, buffers)
glBindFramebuffer(GL_FRAMEBUFFER, 0)
tex_lut = glGenTextures(1)
glBindTexture(GL_TEXTURE_1D, tex_lut)
glTexStorage1D(GL_TEXTURE_1D, 1, GL_R32F, 20)
glTexSubImage1D(GL_TEXTURE_1D, 0, 0, 20, GL_RED, GL_FLOAT, exposureLUT)
glTexParameteri(GL_TEXTURE_1D, GL_TEXTURE_MIN_FILTER, GL_LINEAR)
glTexParameteri(GL_TEXTURE_1D, GL_TEXTURE_MAG_FILTER, GL_LINEAR)
glTexParameteri(GL_TEXTURE_1D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE)
myobject.load("torus.sbm")
glGenBuffers(1, ubo_transform)
glBindBuffer(GL_UNIFORM_BUFFER, ubo_transform)
glBufferData(GL_UNIFORM_BUFFER, (2 + SPHERE_COUNT) * glm.sizeof(glm.mat4), None, GL_DYNAMIC_DRAW)
class material:
diffuse_color = glm.vec3
specular_color = glm.vec3
specular_power = GLfloat(0)
ambient_color = glm.vec3
glGenBuffers(1, ubo_material)
glBindBuffer(GL_UNIFORM_BUFFER, ubo_material)
size_material = ctypes.sizeof(ctypes.c_float) * 12;
glBufferData(GL_UNIFORM_BUFFER, SPHERE_COUNT * size_material, None, GL_STATIC_DRAW)
mat = glMapBufferRange(GL_UNIFORM_BUFFER, 0, SPHERE_COUNT * size_material, GL_MAP_WRITE_BIT | GL_MAP_INVALIDATE_BUFFER_BIT)
m = (GLfloat * 12 * SPHERE_COUNT).from_address(mat)
ambient = 0.002
for i in range(SPHERE_COUNT):
fi = 3.14159267 * i / 8.0
m[i][0:3] = (ctypes.c_float * 3)(sin(fi) * 0.5 + 0.5, sin(fi + 1.345) * 0.5 + 0.5, sin(fi + 2.567) * 0.5 + 0.5)
m[i][4:7] = (ctypes.c_float * 3)(2.8, 2.8, 2.9)
m[i][7] = 30
m[i][8:11] = (ctypes.c_float * 3)(ambient * 0.025, ambient * 0.025, ambient * 0.025)
ambient *= 1.5
glUnmapBuffer(GL_UNIFORM_BUFFER)
def display(self):
global program_filter
global program_resolve
global program_render
global tex_filter
global exposure
global vao
global filter_fbo
global ubo_transform
global ubo_material
global bloom_thresh_min
global bloom_thresh_max
global uniforms
global tex_brightpass
global myobject
global render_fbo
currentTime = time.time()
black = [ 0.0, 0.0, 0.0, 1.0 ]
one = 1.0
last_time = 0.0
total_time = 0.0
if (not paused):
total_time += (currentTime - last_time)
last_time = currentTime
t = total_time
glViewport(0, 0, self.width, self.height)
glBindFramebuffer(GL_FRAMEBUFFER, render_fbo)
glClearBufferfv(GL_COLOR, 0, black)
glClearBufferfv(GL_COLOR, 1, black)
glClearBufferfv(GL_DEPTH, 0, one)
glEnable(GL_DEPTH_TEST)
glDepthFunc(GL_LESS)
glUseProgram(program_render)
glBindBufferBase(GL_UNIFORM_BUFFER, 0, ubo_transform)
class transforms_t:
mat_proj = glm.mat4
mat_view = glm.mat4
mat_model = [glm.mat4 for _ in range(SPHERE_COUNT)]
size_transforms_t = glm.sizeof(glm.mat4) * (SPHERE_COUNT+2)
mbuffer = glMapBufferRange(GL_UNIFORM_BUFFER, 0, size_transforms_t, GL_MAP_WRITE_BIT | GL_MAP_INVALIDATE_BUFFER_BIT)
bufferp = (GLfloat * 16 * (SPHERE_COUNT+2)).from_address(mbuffer)
mat_proj = (GLfloat * 16)(*identityMatrix)
mat_proj = m3dPerspective(m3dDegToRad(50.0), float(self.width) / float(self.height), 1.0, 1000.0)
T = (GLfloat * 16)(*identityMatrix)
m3dTranslateMatrix44(T, 0.0, 0.0, -20.0)
bufferp[0] = mat_proj
bufferp[1] = T
for i in range(2, SPHERE_COUNT+2):
fi = 3.141592 * i / 16.0
# // float r = cosf(fi * 0.25f) * 0.4f + 1.0f
r = 0.6 if (i & 2) else 1.5
T1 = (GLfloat * 16)(*identityMatrix)
m3dTranslateMatrix44(T1, cos(t + fi) * 5.0 * r, sin(t + fi * 4.0) * 4.0, sin(t + fi) * 5.0 * r)
RY = (GLfloat * 16)(*identityMatrix)
m3dRotationMatrix44(RY, currentTime * m3dDegToRad(30.0) * fi, sin(t + fi * 2.13) * 75.0, cos(t + fi * 1.37) * 92.0, 0.0)
m_model = (GLfloat * 16)(*identityMatrix)
m_model = m3dMultiply(T1, RY)
bufferp[i] = m_model
glUnmapBuffer(GL_UNIFORM_BUFFER)
glBindBufferBase(GL_UNIFORM_BUFFER, 1, ubo_material)
glUniform1f(uniforms.scene.bloom_thresh_min, bloom_thresh_min)
glUniform1f(uniforms.scene.bloom_thresh_max, bloom_thresh_max)
myobject.render(SPHERE_COUNT)
glDisable(GL_DEPTH_TEST)
glUseProgram(program_filter)
glBindVertexArray(vao)
glBindFramebuffer(GL_FRAMEBUFFER, filter_fbo[0])
glBindTexture(GL_TEXTURE_2D, tex_brightpass)
glViewport(0, 0, self.height, self.width)
glDrawArrays(GL_TRIANGLE_STRIP, 0, 4)
glBindFramebuffer(GL_FRAMEBUFFER, filter_fbo[1])
glBindTexture(GL_TEXTURE_2D, tex_filter[0])
glViewport(0, 0, self.width, self.height)
glDrawArrays(GL_TRIANGLE_STRIP, 0, 4)
glUseProgram(program_resolve)
glUniform1f(uniforms.resolve.exposure, exposure)
if (show_prefilter):
glUniform1f(uniforms.resolve.bloom_factor, 0.0)
glUniform1f(uniforms.resolve.scene_factor, 1.0)
else:
glUniform1f(uniforms.resolve.bloom_factor, bloom_factor if show_bloom else 0.0)
glUniform1f(uniforms.resolve.scene_factor, 1.0 if show_scene else 0.0 )
glBindFramebuffer(GL_FRAMEBUFFER, 0)
glActiveTexture(GL_TEXTURE1)
glBindTexture(GL_TEXTURE_2D, tex_filter[1])
glActiveTexture(GL_TEXTURE0)
glBindTexture(GL_TEXTURE_2D, tex_brightpass if show_prefilter else tex_scene)
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
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 Bloom')
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 from: hdrbloom.cpp a Superbible Opengl example 7th ed. p.490
First of all note, that the memory layout of the (std140) structure in the uniform block
struct material_t
{
vec3 diffuse_color;
vec3 specular_color;
float specular_power;
vec3 ambient_color;
};
is
diffuse_color : 3 floats (x, y, z), 1 float alignment
specular_color : 3 floats (x, y, z),
specular_power : 1 float,
ambient_color : 3 floats (x, y, z), 1 float alignment
Please read Should I ever use a vec3 inside of a uniform buffer or shader storage buffer object?
and see OpenGL 4.6 API Core Profile Specification; 7.6.2.2 Standard Uniform Block Layout, page 144.
Thus the size of the buffer is 12 * ctypes.sizeof(ctypes.c_float)
size_material = ctypes.sizeof(ctypes.c_float) * 12;
Create the buffer for the uniform block array:
layout (binding = 1, std140) uniform MATERIAL_BLOCK
{
material_t material[32];
} materials;
glBufferData(GL_UNIFORM_BUFFER, SPHERE_COUNT * size_material, None, GL_STATIC_DRAW)
Map a 2 dimensional array, with shape (SPHERE_COUNT, 12) to the buffer memory:
mat = glMapBufferRange(GL_UNIFORM_BUFFER, 0, SPHERE_COUNT * size_material, GL_MAP_WRITE_BIT | GL_MAP_INVALIDATE_BUFFER_BIT)
m = (GLfloat * 12 * SPHERE_COUNT).from_address(mat)
Assign the values in the loop by array assignment:
ambient = 0.002
for i in range(SPHERE_COUNT):
fi = 3.14159267 * i / 8.0
m[i][0:3] = (ctypes.c_float * 3)(sin(fi) * 0.5 + 0.5, sin(fi + 1.345) * 0.5 + 0.5, sin(fi + 2.567) * 0.5 + 0.5)
m[i][4:7] = (ctypes.c_float * 3)(2.8, 2.8, 2.9)
m[i][7] = 30
m[i][8:11] = (ctypes.c_float * 3)(ambient * 0.025, ambient * 0.025, ambient * 0.025)
ambient *= 1.5
The size of the 2nd uniform block
layout (binding = 0, std140) uniform TRANSFORM_BLOCK
{
mat4 mat_proj;
mat4 mat_view;
mat4 mat_model[32];
} transforms;
is
size_transforms_t = glm.sizeof(glm.mat4) * (SPHERE_COUNT+2)
Map a 2 dimensional array, with shape ((SPHERE_COUNT+2), 16) to the buffer memory:
mbuffer = glMapBufferRange(GL_UNIFORM_BUFFER, 0, size_transforms_t, GL_MAP_WRITE_BIT | GL_MAP_INVALIDATE_BUFFER_BIT)
bufferp = (GLfloat * 16 * (SPHERE_COUNT+2)).from_address(mbuffer)
SBMObject.render_sub_object doesn't render the instances. It has to be somthing like:
def render_sub_object(self, object_index, instance_count, base_instance):
global index_type
glBindVertexArray(self.vao)
if instance_count == 0:
glDrawArrays(GL_TRIANGLES, 0, self.vertexcount)
else:
glDrawArraysInstancedBaseInstance(GL_TRIANGLES,
0,
self.vertexcount,
instance_count,
base_instance)
# [...]
show_prefilter, show_bloom and show_scene are boolean values, they'll never be equal 0.0. Either the uniform float bloom_factor or float scene_factor has to be grater then 0.0, else the fragment color is black.
glUniform1f(uniforms.resolve.bloom_factor, bloom_factor if show_bloom==0 else 0.0)
glUniform1f(uniforms.resolve.scene_factor, 1.0 if show_scene==0 else 0.0)
glUniform1f(uniforms.resolve.bloom_factor, bloom_factor if show_bloom else 0.0)
glUniform1f(uniforms.resolve.scene_factor, 1.0 if show_scene else 0.0 )
glBindTexture(GL_TEXTURE_2D, tex_brightpass if show_prefilter==0 else tex_scene)
glBindTexture(GL_TEXTURE_2D, tex_brightpass if show_prefilter else tex_scene)
The latest code below is a ported python program of the tunnel.cpp program from Superbible OpenGL 7th edition.
The cube appears, however the textures do not. There is also supposed to be slight movement toward the tunnel, and that's not happening either. Any ideas what could be causing this?
Update: Thanks to Rabbid76 the textures now appear and they are rendering correctly.
#!/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
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]
render_prog = GLuint(0)
render_vao = GLuint(0)
class uniforms:
mvp = GLint
offset = GLint
tex_wall = GLuint(0)
tex_ceiling = GLuint(0)
tex_floor = GLuint(0)
uniform = uniforms()
class Scene:
def __init__(self, width, height):
global render_prog
global render_vao
global uniform
global tex_wall, tex_ceiling, tex_floor
self.width = width
self.height = height
vs = GLuint(0)
fs = GLuint(0)
vs_source = '''
#version 420 core
out VS_OUT
{
vec2 tc;
} vs_out;
uniform mat4 mvp;
uniform float offset;
void main(void)
{
const vec2[4] position = vec2[4](vec2(-0.5, -0.5),
vec2( 0.5, -0.5),
vec2(-0.5, 0.5),
vec2( 0.5, 0.5));
vs_out.tc = (position[gl_VertexID].xy + vec2(offset, 0.5)) * vec2(30.0, 1.0);
gl_Position = mvp * vec4(position[gl_VertexID], 0.0, 1.0);
}
'''
fs_source = '''
#version 420 core
layout (location = 0) out vec4 color;
in VS_OUT
{
vec2 tc;
} fs_in;
layout (binding = 0) uniform sampler2D tex;
void main(void)
{
color = texture(tex, fs_in.tc);
}
'''
vs = glCreateShader(GL_VERTEX_SHADER)
glShaderSource(vs, vs_source)
glCompileShader(vs)
glGetShaderInfoLog(vs)
fs = glCreateShader(GL_FRAGMENT_SHADER)
glShaderSource(fs, fs_source)
glCompileShader(fs)
glGetShaderInfoLog(vs)
render_prog = glCreateProgram()
glAttachShader(render_prog, vs)
glAttachShader(render_prog, fs)
glLinkProgram(render_prog)
glDeleteShader(vs)
glDeleteShader(fs)
glGetProgramInfoLog(render_prog)
uniform.mvp = glGetUniformLocation(render_prog, "mvp")
uniform.offset = glGetUniformLocation(render_prog, "offset")
glGenVertexArrays(1, render_vao)
glBindVertexArray(render_vao)
ktxobj = KTXObject()
tex_wall = ktxobj.ktx_load("brick.ktx")
tex_ceiling = ktxobj.ktx_load("ceiling.ktx")
tex_floor = ktxobj.ktx_load("floor.ktx")
textures = [ tex_floor, tex_wall, tex_ceiling ]
for i in range (0, 3):
glBindTexture(GL_TEXTURE_2D, textures[i])
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR)
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR)
glBindVertexArray(render_vao)
def display(self):
green = [ 0.0, 0.1, 0.0, 0.0 ]
currentTime = time.time()
glViewport(0, 0, self.width, self.height)
glClearBufferfv(GL_COLOR, 0, green)
glUseProgram(render_prog)
proj_matrix = (GLfloat * 16)(*identityMatrix)
proj_matrix = m3dPerspective(m3dDegToRad(60.0), float(self.width) / float(self.height), 0.1, 100.0)
glUniform1f(uniform.offset, -(currentTime * 0.03) % 1) # negative sign to postive changes direction
textures = [ tex_wall, tex_ceiling, tex_wall, tex_floor ]
for i in range(0, 4):
RZ = (GLfloat * 16)(*identityMatrix)
m3dRotationMatrix44(RZ, i * m3dDegToRad(90.0), 0.0, 0.0, 1.0)
T = (GLfloat * 16)(*identityMatrix)
m3dTranslateMatrix44(T, -5, 0, -10)
RY = (GLfloat * 16)(*identityMatrix)
m3dRotationMatrix44(RY, m3dDegToRad(90.0), 0.0, 1.0, 0.0)
S = (GLfloat * 16)(*identityMatrix)
m3dScaleMatrix44(S, 300.0, 10.0, 1.0)
mv_matrix = (GLfloat * 16)(*identityMatrix)
mv_matrix = m3dMultiply(RZ, m3dMultiply(T, m3dMultiply(RY, S)))
mvp = (GLfloat * 16)(*identityMatrix)
mvp = m3dMultiply(proj_matrix , mv_matrix )
glUniformMatrix4fv(uniform.mvp, 1, GL_FALSE, mvp)
glBindTexture(GL_TEXTURE_2D, textures[i]);
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 - Tunnel')
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 program is supposed to appear like:
Update: With the amazing code and insights by Rabbid76 the output is now rendering correctly. An animated gif of the output is below.
Dependency files: brick.ktx , ceiling.ktx , floor.ktx ,
and in 'shared' folder ktxloader.py , sbmath.py
You've to use the global statement to set the variables tex_wall, tex_ceiling, tex_floor in global namespace, in the constructor of Scene:
class Scene:
def __init__(self, width, height):
global tex_wall, tex_ceiling, tex_floor
# [...]
tex_wall = ktxobj.ktx_load("brick.ktx")
tex_ceiling = ktxobj.ktx_load("ceiling.ktx")
tex_floor = ktxobj.ktx_load("floor.ktx")
# [...]
Further there are some issues when you set the model matrices. The y-scale has to be 10.0, to scale the walls to proper height and width:
m3dScaleMatrix44(S, 30.0, 1.0, 1.0)
m3dScaleMatrix44(S, 300.0, 10.0, 1.0)
The translation has to be done before the rotation around the z-axis, because walls, floor and ceiling should be rotated displaced. Scaling has to be done first:
mv_matrix = m3dMultiply(T, m3dMultiply(RZ, m3dMultiply(S, RY)))
mv_matrix = m3dMultiply(RZ, m3dMultiply(T, m3dMultiply(RY, S)))
In "ktxloader" module ptr is used, when the data bytes are read from the bitmap:
glTexSubImage2D(GL_TEXTURE_2D, i, 0, 0, width, height, h.glformat, h.gltype, data[ptr:])
So ptr has to be incremented by h.keypairbyte:
data_start = ptr + h.keypairbytes
dt = data[data_start:]
ptr += h.keypairbytes
The floor and the ceiling are swapped:
textures = [ tex_wall, tex_floor, tex_wall, tex_ceiling ]
textures = [ tex_wall, tex_ceiling, tex_wall, tex_floor ]
Before the texture offset uniform (uniform float offset) is set you've to use % (modulo) operator, because the value in currentTime is top large and doesn't fit in single precision (32 bit) floating point value. Since the texture coordinates are in range [0.0, 1.0], the fraction part of the offset can be calculated by % 1. e.g.:
glUniform1f(uniform.offset, (currentTime * -0.03) % 1)
Closed. This question needs to be more focused. It is not currently accepting answers.
Want to improve this question? Update the question so it focuses on one problem only by editing this post.
Closed 3 years ago.
Improve this question
How to python debug opengl starfield simulation that drawing a black screen? The code seems ok, but obviously something is off. Are there any additional functions such as glGetError that can verify variables to assist in debugging? I believe I have hurdled, coded correctly a glMapBufferRange function call.
UPDATE: With excellent support by Rabbid76 the program is now rendering the same as expected results. Thank you very much!
Expected output:
Support files: starfield_support.zip
Ported from: starfield.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, floor
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_()
render_prog = GLuint(0)
star_texture = GLuint(0)
star_vao = GLuint(0)
star_buffer = GLuint(0)
debugcontext=True
errors_only=True
class UNIFORMS_:
time=0
proj_matrix=0
uniforms = UNIFORMS_()
NUM_STARS = 2000
import random
random.seed (0x13371337)
def random_float():
return random.random()
def checkGLError():
status = glGetError()
if status != GL_NO_ERROR:
raise RuntimeError('gl error %s' % (status,))
#GLDEBUGPROC
def CB_OpenGL_DebugMessage(source, type, id, severity, length, message, userParam):
msg = message[0:length]
print('debug:', msg)
class Scene:
def __init__(self, width, height):
global render_prog
global star_vao
global star_buffer
global uniforms
global star_texture
self.width = width
self.height = height
vs = GLuint(0)
fs = GLuint(0)
fs_source = '''
#version 410 core
layout (location = 0) out vec4 color;
uniform sampler2D tex_star;
flat in vec4 starColor;
void main(void)
{
color = starColor * texture(tex_star, gl_PointCoord);
//color.r = 1.0;
}
'''
vs_source = '''
#version 410 core
layout (location = 0) in vec4 position;
layout (location = 1) in vec4 color;
uniform float time;
uniform mat4 proj_matrix;
flat out vec4 starColor;
void main(void)
{
vec4 newVertex = position;
newVertex.z += time;
newVertex.z = fract(newVertex.z);
float size = (20.0 * newVertex.z * newVertex.z);
starColor = smoothstep(1.0, 7.0, size) * color;
newVertex.z = (999.9 * newVertex.z) - 1000.0;
gl_Position = proj_matrix * newVertex;
gl_PointSize = size;
}
'''
vs = glCreateShader(GL_VERTEX_SHADER)
glShaderSource(vs, vs_source)
glCompileShader(vs)
if not glGetShaderiv(vs, GL_COMPILE_STATUS):
print( 'compile error:' )
print( glGetShaderInfoLog(vs) )
fs = glCreateShader(GL_FRAGMENT_SHADER)
glShaderSource(fs, fs_source)
glCompileShader(fs)
if not glGetShaderiv(fs, GL_COMPILE_STATUS):
print( 'compile error:' )
print( glGetShaderInfoLog(fs) )
render_prog = glCreateProgram()
glAttachShader(render_prog, vs)
glAttachShader(render_prog, fs)
glLinkProgram(render_prog)
if not glGetProgramiv(render_prog, GL_LINK_STATUS):
print( 'link error:' )
print( glGetProgramInfoLog(render_prog) )
glDeleteShader(vs)
glDeleteShader(fs)
uniforms.time = glGetUniformLocation(render_prog, "time")
uniforms.proj_matrix = glGetUniformLocation(render_prog, "proj_matrix")
star_texture = ktxobject.ktx_load("star.ktx")
glGenVertexArrays(1, star_vao)
glBindVertexArray(star_vao)
class star_t:
position = glm.vec3
color = glm.vec3
size_star_t = ctypes.sizeof(ctypes.c_float) * 6; # same as glm.sizeof(glm.vec3) * 2
glGenBuffers(1, star_buffer)
glBindBuffer(GL_ARRAY_BUFFER, star_buffer)
glBufferData(GL_ARRAY_BUFFER, NUM_STARS * size_star_t, None, GL_STATIC_DRAW)
star = glMapBufferRange(GL_ARRAY_BUFFER, 0, NUM_STARS * size_star_t, GL_MAP_WRITE_BIT | GL_MAP_INVALIDATE_BUFFER_BIT)
m = (GLfloat * 6 * NUM_STARS).from_address(star)
for i in range(0, 1000):
m[i][0] = (random_float() * 2.0 - 1.0) * 100.0
m[i][1] = (random_float() * 2.0 - 1.0) * 100.0
m[i][2] = random_float()
m[i][3] = 0.8 + random_float() * 0.2
m[i][4] = 0.8 + random_float() * 0.2
m[i][5] = 0.8 + random_float() * 0.2
glUnmapBuffer(GL_ARRAY_BUFFER)
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, size_star_t, None)
glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, size_star_t, ctypes.c_void_p(glm.sizeof(glm.vec3) ) )
glEnableVertexAttribArray(0)
glEnableVertexAttribArray(1)
def display(self):
global render_prog
global star_vao
global uniforms
currentTime = time.time()
black = [ 0.0, 0.0, 0.0, 0.0 ]
one = [ 1.0 ]
t = currentTime
proj_matrix = (GLfloat * 16)(*identityMatrix)
proj_matrix = m3dPerspective(m3dDegToRad(50.0), float(self.width) / float(self.height), 0.1, 1000.0)
t *= 0.1
t -= floor(t)
glViewport(0, 0, self.width, self.height)
glClearBufferfv(GL_COLOR, 0, black)
glClearBufferfv(GL_DEPTH, 0, one)
glUseProgram(render_prog)
glUniform1f(uniforms.time, t)
glUniformMatrix4fv(uniforms.proj_matrix, 1, GL_FALSE, proj_matrix)
glEnable(GL_BLEND)
glBlendFunc(GL_ONE, GL_ONE)
glBindVertexArray(star_vao)
glEnable(GL_PROGRAM_POINT_SIZE)
glDrawArrays(GL_POINTS, 0, NUM_STARS)
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
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)
glutInitContextVersion(4,1)
glutInitContextProfile(GLUT_CORE_PROFILE)
w1 = glutCreateWindow('OpenGL SuperBible - Starfield')
if debugcontext:
glDebugMessageCallback(CB_OpenGL_DebugMessage, None)
if errors_only:
glDebugMessageControl(GL_DONT_CARE, GL_DONT_CARE, GL_DONT_CARE, 0, None, GL_FALSE)
glDebugMessageControl(GL_DEBUG_SOURCE_API, GL_DEBUG_TYPE_ERROR, GL_DONT_CARE, 0, None, GL_TRUE)
else:
glDebugMessageControl(GL_DONT_CARE, GL_DONT_CARE, GL_DONT_CARE, 0, None, GL_TRUE)
glEnable(GL_DEBUG_OUTPUT)
glEnable(GL_DEBUG_OUTPUT_SYNCHRONOUS)
glDebugMessageInsert(GL_DEBUG_SOURCE_APPLICATION, GL_DEBUG_TYPE_MARKER, 0, GL_DEBUG_SEVERITY_NOTIFICATION, -1, "Starting debug messaging service")
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()
How to python debug opengl [...]
Those days it is common to generate debug output when debugging OpenGL.
The Kronos wiki page Debug Output tells everything about it what you've to know.
A debug message callback is specified by glDebugMessageCallback. The debug output has to be enabled by glEnable(GL_DEBUG_OUTPUT). Synchronous outputs can are generated when glEnable(GL_DEBUG_OUTPUT_SYNCHRONOUS) is enabled.
Which outputs are generated can be set by glDebugMessageControl in detail.
To activate the debug output you've to generate a debug call back function with the decorator #GLDEBUGPROC:
#GLDEBUGPROC
def CB_OpenGL_DebugMessage(source, type, id, severity, length, message, userParam):
msg = message[0:length]
print('debug:', msg)
Activate the debug callbacks after creating the window:
if __name__ == '__main__':
glutInit()
glutInitDisplayMode(GLUT_RGBA | GLUT_DOUBLE | GLUT_DEPTH)
glutInitWindowSize(512, 512)
w1 = glutCreateWindow('OpenGL SuperBible - Starfield')
if debugcontext:
glDebugMessageCallback(CB_OpenGL_DebugMessage, None)
if errors_only:
glDebugMessageControl(GL_DONT_CARE, GL_DONT_CARE, GL_DONT_CARE, 0, None, GL_FALSE)
glDebugMessageControl(GL_DEBUG_SOURCE_API, GL_DEBUG_TYPE_ERROR, GL_DONT_CARE, 0, None, GL_TRUE)
else:
glDebugMessageControl(GL_DONT_CARE, GL_DONT_CARE, GL_DONT_CARE, 0, None, GL_TRUE)
glEnable(GL_DEBUG_OUTPUT)
glEnable(GL_DEBUG_OUTPUT_SYNCHRONOUS)
glDebugMessageInsert(GL_DEBUG_SOURCE_APPLICATION, GL_DEBUG_TYPE_MARKER, 0, GL_DEBUG_SEVERITY_NOTIFICATION, -1, "Starting debug messaging service")
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()
Given this snippet:
import textwrap
import math
import numpy as np
from ctypes import *
from OpenGL.GL import *
from OpenGL.GL.ARB.multitexture import *
from OpenGL.GL.ARB.debug_output import *
from OpenGL.GLU import *
from OpenGL.GLUT import *
def make_program(vs, fs):
id_program = glCreateProgram()
glAttachShader(id_program, vs)
glAttachShader(id_program, fs)
glLinkProgram(id_program)
result = glGetProgramiv(id_program, GL_LINK_STATUS)
if not(result):
raise RuntimeError(glGetProgramInfoLog(id_program))
return id_program
def make_fs(source):
id_fs = glCreateShader(GL_FRAGMENT_SHADER)
glShaderSource(id_fs, source)
glCompileShader(id_fs)
result = glGetShaderiv(id_fs, GL_COMPILE_STATUS)
if not(result):
raise Exception("Error: {0}".format(
glGetShaderInfoLog(id_fs)
))
return id_fs
def make_vs(source):
id_vs = glCreateShader(GL_VERTEX_SHADER)
glShaderSource(id_vs, source)
glCompileShader(id_vs)
result = glGetShaderiv(id_vs, GL_COMPILE_STATUS)
if not(result):
raise Exception("Error: {0}".format(
glGetShaderInfoLog(id_vs)
))
return id_vs
def v_length(a):
return math.sqrt(a[0] * a[0] + a[1] * a[1] + a[2] * a[2])
def v_normalize(a):
v = v_length(a)
inv_length = 1.0 / v_length(a)
return [a[0] * inv_length, a[1] * inv_length, a[2] * inv_length]
def v_cross(a, b):
return [
a[1] * b[2] - b[1] * a[2],
a[2] * b[0] - b[2] * a[0],
a[0] * b[1] - b[0] * a[1]
]
def identity():
return [
1, 0, 0, 0,
0, 1, 0, 0,
0, 0, 1, 0,
0, 0, 0, 1
]
def v_dot(a, b):
return a[0] * b[0] + a[1] * b[1] + a[2] * b[2]
def transpose(a):
return [
a[0], a[4], a[8], a[12],
a[1], a[5], a[9], a[13],
a[2], a[6], a[10], a[14],
a[3], a[7], a[11], a[15]
]
def perspective(fovy, aspect, znear, zfar):
tan_half_fovy = math.tan(fovy / 2.0)
m00 = 1.0 / (aspect * tan_half_fovy)
m11 = 1.0 / (tan_half_fovy)
m22 = - (zfar + znear) / (zfar - znear)
m23 = -1.0
m32 = -(2.0 * zfar * znear) / (zfar - znear)
return transpose([
m00, 0.0, 0.0, 0.0,
0.0, m11, 0.0, 0.0,
0.0, 0.0, m22, m32,
0.0, 0.0, m23, 0.0
])
def lookat(eye, target, up):
zaxis = v_normalize(
[target[0] - eye[0], target[1] - eye[1], target[2] - eye[2]]
)
xaxis = v_normalize(v_cross(zaxis, up))
yaxis = v_cross(xaxis, zaxis)
return transpose([
xaxis[0], xaxis[1], xaxis[2], -v_dot(xaxis, eye),
yaxis[0], yaxis[1], yaxis[2], -v_dot(yaxis, eye),
-zaxis[0], -zaxis[1], -zaxis[2], v_dot(zaxis, eye),
0, 0, 0, 1
])
def add_line(data, p0, p1, color):
data += [p0[0], p0[1], p0[2], color[0], color[1], color[2]]
data += [p1[0], p1[1], p1[2], color[0], color[1], color[2]]
def make_axis(k=25.0):
data = []
add_line(data, [k, 0.0, 0.0], [0, 0, 0], [1.0, 0.0, 0.0])
add_line(data, [0.0, k, 0.0], [0, 0, 0], [0.0, 1.0, 0.0])
add_line(data, [-k, 0.0, 0.0], [0, 0, 0], [0.0, 0.0, 1.0])
data = np.array(data, dtype=np.float32)
print("len(data)={} bytes(data)={}".format(
len(data), ArrayDatatype.arrayByteCount(data)))
vao = glGenVertexArrays(1)
glBindVertexArray(vao)
vbo = glGenBuffers(1)
glBindBuffer(GL_ARRAY_BUFFER, vbo)
glBufferData(
GL_ARRAY_BUFFER,
ArrayDatatype.arrayByteCount(data),
data, GL_STATIC_DRAW
)
glEnableVertexAttribArray(0)
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 6 * 4, c_void_p(0))
glEnableVertexAttribArray(1)
glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 6 * 4, c_void_p(3 * 4))
glBindBuffer(GL_ARRAY_BUFFER, 0)
glBindVertexArray(0)
return vao
def draw_axis(id_vao):
glBindVertexArray(id_vao)
glDrawArrays(GL_LINES, 0, 6)
glBindVertexArray(0)
class Mcve():
def __init__(self, window_name, width, height):
self.window_width = width
self.window_height = height
self.window_name = window_name
def init(self):
glEnable(GL_DEPTH_TEST)
glEnable(GL_TEXTURE_2D)
glClearColor(0.0, 0.0, 0.0, 0.0)
self.prog_axis = make_program(
make_vs(textwrap.dedent("""
#version 410
layout (location = 0) in vec2 a_position;
layout (location = 1) in vec3 a_color;
out vec3 color;
uniform mat4 projection;
uniform mat4 view;
uniform mat4 model;
void main () {
color=a_color;
gl_Position = projection*view*model*vec4(a_position, 0.0, 1.0);
}
""")),
make_fs(textwrap.dedent("""
#version 410
in vec3 color;
out vec4 frag_colour;
void main () {
frag_colour = vec4(color,1.0);
}
"""))
)
self.axis = make_axis()
def display(self):
glClearColor(1.0, 1.0, 1.0, 1.0)
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)
eye = [50.0, 50.0, 50.0]
target = [0, 0, 0]
up = [0, 1, 0]
view = lookat(eye, target, up)
projection = perspective(45.0, 1.0, 0.1, 1000.0)
prog = self.prog_axis
glUseProgram(prog)
glUniformMatrix4fv(glGetUniformLocation(prog, "model"), 1, False,
np.array(identity(), dtype=np.float32)
)
glUniformMatrix4fv(glGetUniformLocation(prog, "view"), 1, False,
np.array(view, dtype=np.float32)
)
glUniformMatrix4fv(glGetUniformLocation(prog, "projection"), 1, False,
np.array(projection, dtype=np.float32)
)
draw_axis(self.axis)
glUseProgram(0)
glutSwapBuffers()
def reshape(self, w, h):
self.window_width = w
self.window_height = h
glViewport(0, 0, w, h)
def animate(self):
glutPostRedisplay()
def visible(self, vis):
if (vis == GLUT_VISIBLE):
glutIdleFunc(self.animate)
else:
glutIdleFunc(0)
def key_pressed(self, *args):
key = args[0].decode("utf8")
if key == "\x1b":
sys.exit()
def run(self):
glutInit(sys.argv)
glutInitDisplayMode(
GLUT_RGBA | GLUT_DOUBLE | GLUT_ALPHA | GLUT_DEPTH | GLUT_MULTISAMPLE)
glutInitWindowSize(self.window_width, self.window_height)
glutInitWindowPosition(800, 100)
glutCreateWindow(self.window_name)
glutDisplayFunc(self.display)
glutReshapeFunc(self.reshape)
glutIdleFunc(self.animate)
glutVisibilityFunc(self.visible)
glutKeyboardFunc(self.key_pressed)
self.init()
glutMainLoop()
if __name__ == "__main__":
Mcve(b"MCVE", 800, 600).run()
I get the next output:
At the moment i change the line add_line(data, [-k, 0.0, 0.0], [0, 0, 0], [0.0, 0.0, 1.0]) in the make_axis by add_line(data, [0.0, 0.0, k], [0, 0, 0], [0.0, 0.0, 1.0]) the 3rd axis/line won't be drawn anymore, as shown below:
What's wrong with the code? I've been trying to find out the issue already for quite a while.
The problem lies in the shader, where you assume that all input positions are just 2-dimensional vectors:
layout (location = 0) in vec2 a_position;
gl_Position = projection*view*model*vec4(a_position, 0.0, 1.0);
Since you then statically define the z-coordinate to be 0, [0, 0, -k] will be treated as [0,0,0].
To fix that, just use a vec3 in the shader and don't set the z-coordinate to 0.