Search code examples
pythonglslrenderingpython-moderngl

ModernGL set uniform


I'm considering switching to ModernGL over PyOpenGL, and I'm struggling to implement anything right now.

First of all, I would like to try the classic "triangle that changes shape using a time uniform and sine function", but I'm stuck on how to write to the uniform.

Here is what the documentation says about this:

A uniform is a global GLSL variable declared with the “uniform” storage qualifier. These act as parameters that the user of a shader program can pass to that program.

In ModernGL, Uniforms can be accessed using Program.__getitem__() or Program.__iter__().

# Set a vec4 uniform
uniform['color'] = 1.0, 1.0, 1.0, 1.0

# Optionally we can store references to a member and set the value directly
uniform = program['color']
uniform.value = 1.0, 0.0, 0.0, 0.0

uniform = program['cameraMatrix']
uniform.write(camera_matrix)

This is my code:

import moderngl as mgl
import glfw
import numpy as np
import time
from math import sin

glfw.init()
glfw.window_hint(glfw.CONTEXT_VERSION_MAJOR, 3)
glfw.window_hint(glfw.CONTEXT_VERSION_MINOR, 3)
glfw.window_hint(glfw.OPENGL_PROFILE, glfw.OPENGL_CORE_PROFILE)
window = glfw.create_window(800, 600, "__DELETEME__", None, None)
glfw.make_context_current(window)

context = mgl.create_context()
vertex_source = """
#version 330 core

in vec2 aPos;
uniform float time;

void main() {
    gl_Position = vec4(aPos.x, aPos.y + sin(time), 0.0, 1.0);
}
"""
fragment_source = """
#version 330 core

out vec4 color;

void main(){
    color = vec4(0.0, 0.0, 1.0, 1.0);
}
"""

program = context.program(vertex_shader=vertex_source, fragment_shader=fragment_source)

data = np.array([
    0.5, 0, 
   -0.5, 0, 
    0, 0.5], dtype = "float32")

vbo = context.buffer(data.tobytes())
vao = context.vertex_array(program, vbo, "aPos")
uniform = program["time"]
uniform.value = 1.0

while not glfw.window_should_close(window):
    now = time.time()
    vao.render()
    elapsed = time.time() - now
    glfw.poll_events()
    glfw.swap_buffers(window)
glfw.terminate()

Now it draws nothing. What am I doing wrong? Thanks!


Solution

  • The elapsed time is the difference between the start time and the current time. Get the start time before the application loop and compute the elapsed time in the loop in every frame:

    start_time = time.time()
    while not glfw.window_should_close(window):
        elapsed = time.time() - start_time
    
        # [...]
    

    The value of the uniform "time" has to be updated continuously in the loop:

    while not glfw.window_should_close(window):
        # [...]
    
        uniform.value = elapsed
    

    You have to clear the display in every frame, in the application loop (See ModernGL Context):

    while not glfw.window_should_close(window):
        # [...]
    
        context.clear(0.0, 0.0, 0.0)
        vao.render()
    

    Complete example:

    import moderngl as mgl
    import glfw
    import numpy as np
    import time
    from math import sin
    
    glfw.init()
    glfw.window_hint(glfw.CONTEXT_VERSION_MAJOR, 3)
    glfw.window_hint(glfw.CONTEXT_VERSION_MINOR, 3)
    glfw.window_hint(glfw.OPENGL_PROFILE, glfw.OPENGL_CORE_PROFILE)
    window = glfw.create_window(800, 600, "__DELETEME__", None, None)
    glfw.make_context_current(window)
    
    context = mgl.create_context()
    vertex_source = """
    #version 330 core
    
    in vec2 aPos;
    uniform float time;
    
    void main() {
        gl_Position = vec4(aPos.x, aPos.y + sin(time), 0.0, 1.0);
    }
    """
    fragment_source = """
    #version 330 core
    
    out vec4 color;
    
    void main(){
        color = vec4(0.0, 0.0, 1.0, 1.0);
    }
    """
    
    program = context.program(vertex_shader=vertex_source, fragment_shader=fragment_source)
    
    data = np.array([
        0.5, 0, 
       -0.5, 0, 
        0, 0.5], dtype = "float32")
    
    vbo = context.buffer(data.tobytes())
    vao = context.vertex_array(program, vbo, "aPos")
    uniform = program["time"]
    uniform.value = 1.0
    
    start_time = time.time()
    while not glfw.window_should_close(window):
        elapsed = time.time() - start_time
        uniform.value = elapsed
    
        context.clear(0.0, 0.0, 0.0)
        vao.render()
        
        glfw.poll_events()
        glfw.swap_buffers(window)
    glfw.terminate()