Welcome to ModernGL’s documentation!

Start here.

ModernGL

ModernGL on Github

If you have less time to read this then go to the TL;DR section.

ModernGL and OpenGL

OpenGL is a great environment for developing portable, platform independent, interactive 2D and 3D graphics applications. The API implementation in Python is cumbersome, resulting in applications with high latency. To solve this problem we have developed ModernGL, a wrapper over OpenGL that simplifies the creation of simple graphics applications like scientific simulations, small games or user interfaces. Usually, acquiring in-depth knowledge of OpenGL requires a steep learning curve. In contrast, ModernGL is easy to learn and use, moreover it is capable of rendering with the same performance and quality, with less code written.

How to install?

pip install ModernGL

How to create a window?

ModernGL encapsulates the use of the OpenGL API, a separate module must be used for creating a window. ModernGL can be integrated easily in GLWindow, PyQt5, pyglet, pygame, GLUT and many more.

How to create a context?

  • Create a window
  • Call ModernGL.create_context()

ModernGL

Context

Shaders and Programs

Uniform

Uniform Blocks

Attributes

Varyings

Program Stages

Subroutines
SubroutineUniforms

Buffers

VertexArray

VertexArrayAttributes

Textures

Framebuffers

Renderbuffers

ComputeShaders

InvalidObject

Constants

Enable Flags

Versions

Primitives

Error

Examples

01. Hello World!

01_hello_world

Hello World

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
import struct

import GLWindow
import ModernGL

# Window & Context

wnd = GLWindow.create_window()
ctx = ModernGL.create_context()

# Shaders & Program

prog = ctx.program([
    ctx.vertex_shader('''
        #version 330

        in vec2 vert;

        void main() {
            gl_Position = vec4(vert, 0.0, 1.0);
        }
    '''),
    ctx.fragment_shader('''
        #version 330

        out vec4 color;

        void main() {
            color = vec4(0.3, 0.5, 1.0, 1.0);
        }
    '''),
])

# Buffer

vbo = ctx.buffer(struct.pack(
    '6f',
    0.0, 0.8,
    -0.6, -0.8,
    0.6, -0.8,
))

# Put everything together

vao = ctx.simple_vertex_array(prog, vbo, ['vert'])

# Main loop

while wnd.update():
    ctx.viewport = wnd.viewport
    ctx.clear(0.9, 0.9, 0.9)
    vao.render()

02. Uniforms and Attributes

02_uniforms_and_attributes

Uniforms and Attributes

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
import struct

import GLWindow
import ModernGL

# Window & Context

wnd = GLWindow.create_window()
ctx = ModernGL.create_context()

# Shaders & Program

prog = ctx.program([
    ctx.vertex_shader('''
        #version 330

        in vec2 vert;

        in vec3 vert_color;
        out vec3 frag_color;

        uniform vec2 scale;
        uniform float rotation;

        void main() {
            frag_color = vert_color;
            mat2 rot = mat2(
                cos(rotation), sin(rotation),
                -sin(rotation), cos(rotation)
            );
            gl_Position = vec4((rot * vert) * scale, 0.0, 1.0);
        }
    '''),
    ctx.fragment_shader('''
        #version 330

        in vec3 frag_color;
        out vec4 color;

        void main() {
            color = vec4(frag_color, 1.0);
        }
    '''),
])

# Uniforms

scale = prog.uniforms['scale']
rotation = prog.uniforms['rotation']

width, height = wnd.size
scale.value = (height / width * 0.75, 0.75)

# Buffer

vbo = ctx.buffer(struct.pack(
    '15f',

    1.0, 0.0,
    1.0, 0.0, 0.0,

    -0.5, 0.86,
    0.0, 1.0, 0.0,

    -0.5, -0.86,
    0.0, 0.0, 1.0,
))

# Put everything together

vao = ctx.simple_vertex_array(prog, vbo, ['vert', 'vert_color'])

# Main loop

while wnd.update():
    ctx.viewport = wnd.viewport
    ctx.clear(0.9, 0.9, 0.9)
    rotation.value = wnd.time
    vao.render()

03. Blending

03_alpha_blending

Alpha Blending

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
import struct

import GLWindow
import ModernGL

# Window & Context

wnd = GLWindow.create_window()
ctx = ModernGL.create_context()

# Shaders & Program

prog = ctx.program([
    ctx.vertex_shader('''
        #version 330

        in vec2 vert;

        in vec4 vert_color;
        out vec4 frag_color;

        uniform vec2 scale;
        uniform float rotation;

        void main() {
            frag_color = vert_color;
            mat2 rot = mat2(
                cos(rotation), sin(rotation),
                -sin(rotation), cos(rotation)
            );
            gl_Position = vec4((rot * vert) * scale, 0.0, 1.0);
        }
    '''),
    ctx.fragment_shader('''
        #version 330

        in vec4 frag_color;
        out vec4 color;

        void main() {
            color = vec4(frag_color);
        }
    '''),
])

# Uniforms

scale = prog.uniforms['scale']
rotation = prog.uniforms['rotation']

width, height = wnd.size
scale.value = (height / width * 0.75, 0.75)

# Buffer

vbo = ctx.buffer(struct.pack(
    '18f',

    1.0, 0.0,
    1.0, 0.0, 0.0, 0.5,

    -0.5, 0.86,
    0.0, 1.0, 0.0, 0.5,

    -0.5, -0.86,
    0.0, 0.0, 1.0, 0.5,
))

# Put everything together

vao = ctx.simple_vertex_array(prog, vbo, ['vert', 'vert_color'])

# Main loop

while wnd.update():
    ctx.viewport = wnd.viewport
    ctx.clear(0.9, 0.9, 0.9)
    ctx.enable(ModernGL.BLEND)
    rotation.value = wnd.time
    vao.render(instances=10)

04. Texture

04_textures

Textures

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
import os
import struct

import GLWindow
import ModernGL

from PIL import Image

# Window & Context

wnd = GLWindow.create_window()
ctx = ModernGL.create_context()

# Shaders & Program

prog = ctx.program([
    ctx.vertex_shader('''
        #version 330

        in vec2 vert;
        out vec2 tex_coord;

        uniform vec2 scale;
        uniform float rotation;

        void main() {
            float r = rotation * (0.5 + gl_InstanceID * 0.05);
            mat2 rot = mat2(cos(r), sin(r), -sin(r), cos(r));
            gl_Position = vec4((rot * vert) * scale, 0.0, 1.0);
            tex_coord = vert;
        }
    '''),
    ctx.fragment_shader('''
        #version 330

        uniform sampler2D texture;

        in vec2 tex_coord;
        out vec4 color;

        void main() {
            color = vec4(texture2D(texture, tex_coord).rgb, 1.0);
        }
    '''),
])

# Uniforms

scale = prog.uniforms['scale']
rotation = prog.uniforms['rotation']

width, height = wnd.size
scale.value = (height / width * 0.75, 0.75)

# Buffer

vbo = ctx.buffer(struct.pack(
    '6f',
    1.0, 0.0,
    -0.5, 0.86,
    -0.5, -0.86,
))

# Put everything together

vao = ctx.simple_vertex_array(prog, vbo, ['vert'])

# Texture

img = Image.open(os.path.join(os.path.dirname(__file__), '..', 'data', 'noise.jpg'))
texture = ctx.texture(img.size, 3, img.tobytes())
texture.use()

# Main loop

while wnd.update():
    ctx.viewport = wnd.viewport
    ctx.clear(0.9, 0.9, 0.9)
    rotation.value = wnd.time
    vao.render()

05. Perspective

05_perspective_projection

Perspective Projection

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
import struct

import GLWindow
import ModernGL

# Window & Context

wnd = GLWindow.create_window()
ctx = ModernGL.create_context()

vert = ctx.vertex_shader('''
    #version 330

    in vec3 vert;

    uniform float znear;
    uniform float zfar;
    uniform float fovy;
    uniform float ratio;

    uniform vec3 center;
    uniform vec3 eye;
    uniform vec3 up;

    mat4 perspective() {
        float zmul = (-2.0 * znear * zfar) / (zfar - znear);
        float ymul = 1.0 / tan(fovy * 3.14159265 / 360);
        float xmul = ymul / ratio;

        return mat4(
            xmul, 0.0, 0.0, 0.0,
            0.0, ymul, 0.0, 0.0,
            0.0, 0.0, -1.0, -1.0,
            0.0, 0.0, zmul, 0.0
        );
    }

    mat4 lookat() {
        vec3 forward = normalize(center - eye);
        vec3 side = normalize(cross(forward, up));
        vec3 upward = cross(side, forward);
        return mat4(
            side.x, upward.x, -forward.x, 0,
            side.y, upward.y, -forward.y, 0,
            side.z, upward.z, -forward.z, 0,
            -dot(eye, side), -dot(eye, upward), dot(eye, forward), 1
        );
    }

    void main() {
        gl_Position = perspective() * lookat() * vec4(vert, 1.0);
    }
''')

frag = ctx.fragment_shader('''
    #version 330

    out vec4 color;

    void main() {
        color = vec4(0.04, 0.04, 0.04, 1.0);
    }
''')

width, height = wnd.size

prog = ctx.program([vert, frag])

prog.uniforms['znear'].value = 0.1
prog.uniforms['zfar'].value = 1000.0
prog.uniforms['ratio'].value = width / height
prog.uniforms['fovy'].value = 60

prog.uniforms['eye'].value = (3, 3, 3)
prog.uniforms['center'].value = (0, 0, 0)
prog.uniforms['up'].value = (0, 0, 1)

grid = bytes()

for i in range(0, 65):
    grid += struct.pack('6f', i - 32, -32.0, 0.0, i - 32, 32.0, 0.0)
    grid += struct.pack('6f', -32.0, i - 32, 0.0, 32.0, i - 32, 0.0)

vbo = ctx.buffer(grid)
vao = ctx.simple_vertex_array(prog, vbo, ['vert'])

while wnd.update():
    ctx.viewport = wnd.viewport
    ctx.clear(0.9, 0.9, 0.9)
    vao.render(ModernGL.LINES, 65 * 4)

Julia Fractal

julia_fractal

Julia Fractal

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
import struct

import GLWindow
import ModernGL

# Window & Context

wnd = GLWindow.create_window()
ctx = ModernGL.create_context()

vert = ctx.vertex_shader('''
    #version 330

    in vec2 vert;
    out vec2 tex;

    void main() {
        gl_Position = vec4(vert, 0.0, 1.0);
        tex = vert / 2.0 + vec2(0.5, 0.5);
    }
''')

frag = ctx.fragment_shader('''
    #version 330

    in vec2 tex;
    out vec4 color;

    uniform vec2 center;
    uniform int iter;

    void main() {
        vec2 z = vec2(5.0 * (tex.x - 0.5), 3.0 * (tex.y - 0.5));
        vec2 c = center;

        int i;
        for(i = 0; i < iter; i++) {
            vec2 v = vec2(
                (z.x * z.x - z.y * z.y) + c.x,
                (z.y * z.x + z.x * z.y) + c.y
            );
            if (dot(v, v) > 4.0) break;
            z = v;
        }

        float cm = fract((i == iter ? 0.0 : float(i)) * 10 / iter);
        color = vec4(
            fract(cm + 0.0 / 3.0),
            fract(cm + 1.0 / 3.0),
            fract(cm + 2.0 / 3.0),
            1.0
        );
    }
''')

prog = ctx.program([vert, frag])

vbo = ctx.buffer(struct.pack('8f', -1.0, -1.0, -1.0, 1.0, 1.0, -1.0, 1.0, 1.0))
vao = ctx.simple_vertex_array(prog, vbo, ['vert'])

prog.uniforms['iter'].value = 100

x, y = (0.49, 0.32)

wnd.grab_mouse(True)

while wnd.update():
    ctx.viewport = wnd.viewport
    ctx.clear(0.9, 0.9, 0.9)
    mx, my = wnd.mouse_delta
    x -= mx / 100
    y -= my / 100

    prog.uniforms['center'].value = (y, x)
    vao.render(ModernGL.TRIANGLE_STRIP)

Particle System

particle_system

Particle System

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
import math
import random
import struct

import GLWindow
import ModernGL

# Window & Context

wnd = GLWindow.create_window()
ctx = ModernGL.create_context()

tvert = ctx.vertex_shader('''
    #version 330

    uniform vec2 acc;

    in vec2 in_pos;
    in vec2 in_prev;

    out vec2 out_pos;
    out vec2 out_prev;

    void main() {
        out_pos = in_pos * 2.0 - in_prev + acc;
        out_prev = in_pos;
    }
''')

vert = ctx.vertex_shader('''
    #version 330

    in vec2 vert;

    void main() {
        gl_Position = vec4(vert, 0.0, 1.0);
    }
''')

frag = ctx.fragment_shader('''
    #version 330

    out vec4 color;

    void main() {
        color = vec4(0.30, 0.50, 1.00, 1.0);
    }
''')

prog = ctx.program([vert, frag])

transform = ctx.program(tvert, ['out_pos', 'out_prev'])


def particle():
    a = random.uniform(0.0, math.pi * 2.0)
    r = random.uniform(0.0, 0.001)

    return struct.pack('2f2f', 0.0, 0.0, math.cos(a) * r - 0.003, math.sin(a) * r - 0.008)


vbo1 = ctx.buffer(b''.join(particle() for i in range(1024)))
vbo2 = ctx.buffer(reserve=vbo1.size)

vao1 = ctx.simple_vertex_array(transform, vbo1, ['in_pos', 'in_prev'])
vao2 = ctx.simple_vertex_array(transform, vbo2, ['in_pos', 'in_prev'])

render_vao = ctx.vertex_array(prog, [
    (vbo1, '2f8x', ['vert']),
])

transform.uniforms['acc'].value = (0, -0.0001)

idx = 0

ctx.point_size = 5.0

while wnd.update():
    ctx.viewport = wnd.viewport
    ctx.clear(0.9, 0.9, 0.9)

    for i in range(8):
        vbo1.write(particle(), offset=idx * struct.calcsize('2f2f'))
        idx = (idx + 1) % 1024

    render_vao.render(ModernGL.POINTS, 1024)
    vao1.transform(vbo2, ModernGL.POINTS, 1024)
    ctx.copy_buffer(vbo1, vbo2)

Contributing

TL;DR

Indices and tables