{"id":17143936,"url":"https://github.com/adamlwgriffiths/omgl","last_synced_at":"2025-04-13T11:11:40.714Z","repository":{"id":28366401,"uuid":"31880312","full_name":"adamlwgriffiths/OMGL","owner":"adamlwgriffiths","description":"Pythonic OpenGL Bindings","archived":false,"fork":false,"pushed_at":"2017-12-27T06:55:34.000Z","size":1497,"stargazers_count":17,"open_issues_count":2,"forks_count":0,"subscribers_count":3,"default_branch":"master","last_synced_at":"2025-04-11T14:26:16.758Z","etag":null,"topics":["bsd-license","library","opengl","opengl-framework","opengl-library","python"],"latest_commit_sha":null,"homepage":null,"language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"other","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/adamlwgriffiths.png","metadata":{"files":{"readme":"README.rst","changelog":"CHANGELOG","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2015-03-09T05:04:51.000Z","updated_at":"2025-02-15T16:09:07.000Z","dependencies_parsed_at":"2022-08-26T20:20:38.529Z","dependency_job_id":null,"html_url":"https://github.com/adamlwgriffiths/OMGL","commit_stats":null,"previous_names":[],"tags_count":2,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/adamlwgriffiths%2FOMGL","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/adamlwgriffiths%2FOMGL/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/adamlwgriffiths%2FOMGL/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/adamlwgriffiths%2FOMGL/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/adamlwgriffiths","download_url":"https://codeload.github.com/adamlwgriffiths/OMGL/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248703198,"owners_count":21148118,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["bsd-license","library","opengl","opengl-framework","opengl-library","python"],"created_at":"2024-10-14T20:42:43.145Z","updated_at":"2025-04-13T11:11:40.695Z","avatar_url":"https://github.com/adamlwgriffiths.png","language":"Python","funding_links":[],"categories":[],"sub_categories":[],"readme":"====\nOMGL\n====\n\nAn easy to use Pythonic 3D OpenGL framework.\n\nOMGL is the Graphical core of the `Bast 3D engine \u003chttps://github.com/adamlwgriffiths/bast\u003e`_.\n\nInspired by the following projects:\n\n* `PyGLy \u003chttps://github.com/adamlwgriffiths/PyGLy\u003e`_\n* `PyGL \u003chttps://github.com/Ademan/pygl\u003e`_\n* `Glitter \u003chttps://github.com/swenger/glitter\u003e`_\n\n\nFeatures\n========\n\n* Pythonic - Don't worry about OpenGL's horrible state machine.\n* NumPy at the Core - Easily pass complex data structures to OpenGL.\n* BYOWS - Bring your own windowing system (`CyGLFW3 recommended \u003chttps://github.com/adamlwgriffiths/cyglfw3\u003e`_).\n\n\n\nDependencies\n============\n\n* Numpy (A recent version as 1.9 has a `critical bug \u003chttps://github.com/numpy/numpy/issues/5224\u003e`_)\n* PyOpenGL\n* Pillow\n\n\nOptional dependencies dependencies\n==================================\n\n* PyOpenGL-accelerate - Speed boost for PyOpenGL\n    * `pip install -r requirements-accelerate.txt`\n    * or `pip install omgl[accelerate]`\n* CyGLFW3 - Windowing system used in examples (or other windowing system)\n    * `pip install -r requirements-cyglfw3.txt`\n    * or `pip install omgl[cyglfw3]`\n* Pyrr - 3D Mathematics library used in examples\n    * `pip install -r requirements-pyrr.txt`\n    * or `pip install omgl[pyrr]`\n\nTo install all dependencies:\n\n* `pip install omgl[accelerate,cyglfw3,pyrr]`\n\n\nExamples\n========\n\n\nDebugging\n---------\n\nYou can print out each OpenGL call you or OMGL make by enabling function printing.\nThis is excellent for debugging OpenGL issues or inspecting the OMGL call logic.\n\nEssentially this replaces all the PyOpenGL glX functions with a proxy function that\nprints the functions name and arguments before calling the original function.\n\n::\n\n    from omgl import debug\n    debug.print_gl_calls(True)\n\n\n\nNumpy Dtypes\n------------\n\nIt is often important to convert between OpenGL and Numpy dtypes for variable types\nand enumerations.\n\nThis is easily done with the dtypes module.\n\n::\n\n    from OpenGL import GL\n    from omgl import dtypes\n    print(dtypes.uint8.dtype)\n    print(dtypes.uint8.gl_type)\n    print(dtypes.uint8.gl_enum)\n    print(dtypes.uint8.char_code)\n\n    d = dtypes.for_enum(GL.GL_UNSIGNED_INT)\n    print(d.gl_enum, d.dtype)\n\n    d = dtypes.for_dtype(np.float32)\n    print(d.gl_enum, d.dtype)\n\n    d = dtypes.for_code('f')\n    print(d.gl_enum, d.dtype)\n\n\n\nTextures\n--------\n\nTextures can be created using raw data.\n\n::\n\n    from omgl.texture import Texture2D\n    data = np.random.random((32,32,4))\n    data *= 255\n    data = data.astype(np.uint8)\n\n    texture = Texture2D(data)\n\n    texture.active_unit = 0\n    texture.bind()\n\n\nOr loaded from a file using PIL / Pillow\n\n::\n\n    from omgl.texture import Texture2D\n    texture = Texture2D.open('assets/texture/formats/RGBA.png')\n\n\nTextures also provide information about themselves.\n\n::\n\n\n    from omgl.texture import Texture2D\n    texture = Texture2D.open('assets/texture/formats/RGBA.png')\n\n    print(texture.internal_format)\n    print(texture.size)\n    print(texture.dtype)\n    print(texture.shape)\n\n\nTextures can also be created empty using a shape and dtype.\nPassed formats and Internal formats are auto-detected, but can be over-ridden\nwith the internal_format and format arguments.\n\n::\n\n    from OpenGL import GL\n    from omgl.texture import Texture2D\n    # create an empty 256x256 texture with 4 channels, RGBA.\n    texture = Texture2D(shape=(256,256,4), dtype=np.uint8)\n    texture.set_data(data=np.random.random((256,256,4)))\n\n    texture = Texture2D(shape=(256,256,4), dtype=np.uint8, internal_format=GL.GL_RGBA)\n\n\n\nTextures will automatically have their min and mag filters set to GL_LINEAR\nto avoid 'black textures' in OpenGL 3.\nThis can be avoided by passing the desired filter mode to the constructor as the\nmin_filter and mag_filter properties.\n\n::\n\n    from OpenGL import GL\n    from omgl.texture import Texture2D\n    texture = Texture2D.open('assets/texture/formats/RGBA.png',\n        min_filter=GL.GL_NEAREST,\n        mag_filter=GL.GL_NEAREST,\n    )\n\n\nMipMap's are automatically generated, and can be re-generated with the 'mipmap' function.\nAutomatic generation of mipmaps can be disabled by over-riding the mipmap argument\nin the constructor and set_data functions.\n\n::\n\n    from omgl.texture import Texture2D\n    texture = Texture2D(shape=(256,256,4), dtype=np.uint8, mipmap=False)\n    texture.set_data(data=np.random.random((256,256,4)), mipmap=False)\n    texture.mipmap()\n    texture.set_data(data=np.random.random((256,256,4)), mipmap=False)\n    texture.mipmap()\n\n\n\nBecause of the need for texture minification and magnification, textures consist\nof a number of 'levels'. This also means that getting and setting data must specify\nthe requested level.\nBy default this is 0.\n\n::\n\n    from omgl.texture import Texture2D\n    texture = Texture2D(shape=(256,256,4), dtype=np.uint8, mipmap=False)\n    texture.set_data(np.random.random((256,256,4)))\n    texture.mipmap()\n\n    # get base level texture data\n    print(texture.get_data())\n\n    # get first mipmap level data\n    print(texture.get_data(1))\n\n    # a convenience property is provided for level 0\n    texture.data = np.random.random((256,256,4))\n    print(texture.data)\n\n\nVarious texture parameters can be set at creation via named arguments, or later.\n\n::\n\n    from omgl.texture import Texture2D\n    texture = Texture2D.open('assets/texture/formats/RGBA.png', wrap_s=GL.GL_REPEAT)\n    texture.wrap_t = GL.GL_CLAMP_TO_EDGE\n\n\nThe active texture unit can be set from the Texture class (or derived classes)\nor texture objects themselves.\nNote that this property accesses the global active unit property, and isn't\nsetting that object's texture unit.\n\n::\n\n    from omgl.texture import Texture, Texture2D\n    texture = Texture2D.open('assets/texture/formats/RGBA.png')\n\n    # the following calls are all equivalent\n    texture.active_unit = 1\n    Texture.active_unit = 1\n    Texture2D.active_unit = 1\n\n    # we can get the current active unit.\n    print(Texture.active_unit)\n\n\nBuffers\n-------\n\n\nBuffers make use of complex numpy dtype's. This let's OMGL automatically tell OpenGL\nabout the layout of your buffers.\n\n::\n\n    from omgl.buffer import VertexBuffer\n    data = np.array([\n        [([ 1., 0.,-1.], [1., 0.])],\n        [([-1., 0.,-1.], [0., 0.])],\n        [([ 0., 1.,-1.], [.5, 1.])],\n        ],\n        dtype=[('in_position', np.float32, 3,),('in_uv', np.float32, 2,),]\n    )\n    vb = VertexBuffer(data)\n\n\nTo avoid the complexities of creating the array, you can convert to a complex array after creation.\n\n::\n\n    from omgl.buffer import VertexBuffer\n    flat_data = np.array([\n        [ 1., 0.,-1., 1., 0.],\n        [-1., 0.,-1., 0., 0.],\n        [ 0., 1.,-1., .5, 1.],\n    ])\n    data = flat_data.view(dtype=[('in_position', np.float32, 3,),('in_uv', np.float32, 2,),])\n    vb = VertexBuffer(data)\n\n\nYou can manually provide this information should you not want to use complex dtypes.\n\n::\n\n    from omgl.buffer import VertexBuffer, BufferPointer\n    flat_data = np.array([\n        [ 1., 0.,-1., 1., 0.],\n        [-1., 0.,-1., 0., 0.],\n        [ 0., 1.,-1., .5, 1.],\n    ])\n    data = flat_data.view(dtype=[('in_position', np.float32, 3,),('in_uv', np.float32, 2,),])\n    vb = VertexBuffer(data)\n\n    # pointer to vertex data\n    # total size of an element is 5 * 32bit floats\n    stride = 5 * np.dtype(np.float32).itemsize\n    vertex_ptr = BufferPointer(vb, count=3, stride=stride, offset=0, dtype=np.float32)\n\n    # pointer of uv data\n    # offset of uv's is the vertex x,y,z, which is 3 * 32bit float.\n    offset = 3 * np.dtype(np.float32).itemsize\n    uv_tr = BufferPointer(vb, count=2, stride=stride, offset=offset, dtype=np.float32)\n\n\nOr use the entire array as a single data type\n\n::\n\n    from omgl.buffer import VertexBuffer, BufferPointer\n    vertex_data = np.array([\n        [ 1., 0.,-1.],\n        [-1., 0.,-1.],\n        [ 0., 1.,-1.],\n    ], dtype=np.float32)\n    vertices = VertexBuffer(vertex_data)\n\n    # this data is 2 dimensional to make it easier to read\n    # it could be 1 dimensional, with no code changes, if we wished\n    uv_data = np.array([\n        [1., 0.],\n        [0., 0.],\n        [0.5, 1.],\n    ], dtype=np.float32)\n    uvs = VertexBuffer(uv_data)\n\n\nTexture Buffer's allow like access to 1 dimensional buffer data.\nThis is great for passing large amounts of random-access data to shaders.\n\n::\n\n    from omgl.buffer import TextureBuffer\n    data = np.ones((32,32,4), dtype=np.float32)\n    texture_buffer = TextureBuffer(data)\n    texture = texture_buffer.texture\n\n    texture.active_unit = 0\n    texture.bind()\n\n\n\nShaders\n-------\n\nShader and Program objects wrap GLSL shaders.\nAttributes must be set at construction time.\n\n::\n\n    from omgl.shader import VertexShader, FragmentShader, Program\n    # vertex shader\n    vs = \"\"\"\n        #version 400\n        in vec3 in_position;\n        in vec2 in_uv;\n        uniform mat4 in_projection;\n        uniform mat4 in_model_view;\n        out vec2 ex_uv;\n        void main() {\n            gl_Position = in_projection * in_model_view * vec4(in_position, 1.0);\n            ex_uv = in_uv;\n        }\n        \"\"\"\n\n    # fragment shader\n    fs = \"\"\"\n        #version 400\n        uniform sampler2D in_diffuse_texture;\n        in vec2 ex_uv;\n        out vec4 out_color;\n        void main(void) {\n            out_color = texture(in_diffuse_texture, ex_uv);\n        }\n        \"\"\"\n\n    # create and link\n    # specify attributes at link time\n    program = Program([\n        VertexShader(vs),\n        FragmentShader(fs),\n        ],\n        in_position=1,\n        in_uv=2,\n    )\n\n    # these values can be set automatically using a Pipeline\n    with program:\n        program.in_projection = np.eye(4)\n        program.in_model_view = np.eye(4)\n        # set the texture unit to read from\n        program.in_diffuse_texture = 0\n\n\nOr load shaders from a file\n\n::\n\n    from omgl.shader import FragmentShader, VertexShader, Program\n    program = Program([\n        VertexShader.open('assets/shaders/test.vs'),\n        FragmentShader.open('assets/shaders/test.fs'),\n        ],\n        in_position=1,\n        in_uv=2,\n    )\n\n\nShader Programs automatically find and provide wrappers for their Uniform and\nAttribute variables.\nUniform data can be read or set easily.\n\nVariable's are loaded from OpenGL only when accessed, meaning you won't get any\npipeline stalls while loading shaders in parallel.\n\n\n::\n\n    from omgl.shader import FragmentShader, VertexShader, Program\n    program = Program([\n        VertexShader.open('assets/shaders/test.vs'),\n        FragmentShader.open('assets/shaders/test.fs'),\n        ],\n        in_position=1,\n        in_uv=2,\n    )\n\n    # get a variable directly\n    program.in_position\n\n    # get an attribute from the attributes dict\n    program.attributes['in_position']\n\n    # print a list of attribute variable names\n    print(program.attributes.keys())\n\n    # inspect an attribute\n    print(program['in_position'].location)\n    print(program['in_position'].dtype)\n    print(program['in_position'].itemsize)\n    print(program['in_position'].nbytes)\n    print(program['in_position'].dimensions)\n    print(program['in_position'].enum)\n    print(program['in_position'].name)\n    print(program['in_position'].enum)\n\n    # get a uniform directly\n    program.in_projection\n\n    # get a uniform from the uniforms dict\n    program.uniforms['in_projection']\n\n    # print a list of uniform variable names\n    print(program.uniforms.keys())\n\n    # inspect a uniform\n    print(program['in_projection'].location)\n    print(program['in_projection'].dtype)\n    print(program['in_projection'].itemsize)\n    print(program['in_projection'].nbytes)\n    print(program['in_projection'].dimensions)\n    print(program['in_projection'].enum)\n    print(program['in_projection'].name)\n    print(program['in_projection'].enum)\n\n    # print the current value of the uniform\n    print(program['in_projection'].data)\n\n    # set the value of the uniform\n    program['in_projection'].data = np.eye(4)\n\n\n\nPipelines\n---------\n\nPipelines provide a way to automatically link textures, values (uniforms) and\nvertex data (attributes) to a shader program before rendering.\n\nThe Pipeline allows you to assign values to the shader program without worrying about it being bound or not at the time.\n\nThis lets you decouple the shader program from the renderable object itself.\n\n\n::\n\n    from OpenGL import GL\n    from omgl.shader import FragmentShader, VertexShader, Program\n    from omgl.buffer import VertexBuffer, VertexArray\n    from omgl.pipeline.pipeline import Pipeline\n\n    # shader program\n    program = Program([\n        VertexShader.open('assets/shaders/test.vs'),\n        FragmentShader.open('assets/shaders/test.fs'),\n        ],\n        in_position=1,\n        in_uv=2,\n    )\n\n    # vertex data\n    data = np.array([\n        [([ 1., 0.,-1.], [1., 0.])],\n        [([-1., 0.,-1.], [0., 0.])],\n        [([ 0., 1.,-1.], [.5, 1.])],\n        ],\n        dtype=[('in_position', np.float32, 3,),('in_uv', np.float32, 2,),]\n    )\n    vb = VertexBuffer(data)\n\n    # bind the vertex attributes to a vertex array\n    va = VertexArray()\n\n    # bind the vertex array and notify it of our vertex pointers\n    with va:\n        # get the location of the attributes\n        in_position = program.attributes.get('in_position').location\n        va[in_position] = vb.pointers['in_position']\n\n        in_uv = program.attributes.get('in_uv')\n        va[in_uv] = vb.pointers['in_uv']\n\n\n    # load our texture\n    texture = Texture2D.open('assets/textures/formats/RGBA.png')\n\n    # create a pipeline for our shader\n    # the pipeline will automatically assign these uniforms to any matching\n    # variable in our shader\n    # we can pass any variables we want at construction time as named parameters\n    pipeline = Pipeline(program,\n        in_diffuse_texture=texture,\n    )\n\n    # we can also set any value after creation, there is no difference\n    pipeline.in_projection = np.eye(4)\n    pipeline.in_model_view = np.eye(4)\n    # textures are automatically handled\n    # variables that don't exist in the shader are ignored\n    pipeline.this_variable_doesnt_exist = (1,2,3,4)\n\n    # bind the pipeline\n    # this will actually bind the shader program and push any values into it\n    with pipeline:\n        # tell the vertex array to render our vertex data as lists of triangles\n        va.render(GL.GL_TRIANGLES)\n\n\nMeshes\n------------------\n\nMeshes greatly simplify the boilerplate required to render an object\nby wrapping a lot of the above functionality.\nMeshes handle vertex arrays, shaders and pipelines for you.\n\n\n::\n\n    from OpenGL import GL\n    from omgl.shader import FragmentShader, VertexShader, Program\n    from omgl.buffer import VertexBuffer\n    from omgl.pipeline.pipeline import Pipeline\n    from omgl.mesh import Mesh\n\n    # shader program\n    program = Program([\n        VertexShader.open('assets/shaders/test.vs'),\n        FragmentShader.open('assets/shaders/test.fs'),\n        ],\n        in_position=1,\n        in_uv=2,\n    )\n\n    # vertex data\n    data = np.array([\n        [([ 1., 0.,-1.], [1., 0.])],\n        [([-1., 0.,-1.], [0., 0.])],\n        [([ 0., 1.,-1.], [.5, 1.])],\n        ],\n        dtype=[('in_position', np.float32, 3,),('in_uv', np.float32, 2,),]\n    )\n    vb = VertexBuffer(data)\n\n    # load our texture\n    texture = Texture2D.open('assets/textures/formats/RGBA.png')\n\n    # create a pipeline with our shader and our texture\n    pipeline = Pipeline(program, in_diffuse_texture=texture)\n\n    # create a mesh using our pipeline and vertex data\n    mesh = Mesh(pipeline, **vb.pointers)\n\n    # render the mesh automatically\n    # we can pass in any frame-to-frame here as named arguments\n    mesh.render(in_projection=np.eye(4), in_model_view=np.eye(4))\n\n\nBy default, meshes render all vertex data and use GL_TRIANGLES as the primitive\ntype.\n\nThis can be changed at mesh construction time.\n\n::\n\n    from omgl.buffer import IndexBuffer\n    indices = IndexBuffer(np.array([1,2,3,4,5,6], dtype=np.uint32))\n    mesh = Mesh(pipeline, indices=indices, primitive=GL.GL_TRIANGLE_STRIP)\n\n\nIf vertex buffer's contain mixed primitive types, then use multiple meshes\nwith different pointers into the data.\nTo control which elements are rendered, use either an IndexBuffer, or render from\nthe mesh's VertexArray directly.\n\n::\n\n    mesh.vertex_array.render(GL.GL_TRIANGLE_STRIP, start=5, count=10)\n    mesh.vertex_array.render(GL.GL_TRIANGLES, start=20, count=6)\n\n\nAuthors\n=======\n\n* `Adam Griffiths \u003chttps://github.com/adamlwgriffiths\u003e`_\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fadamlwgriffiths%2Fomgl","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fadamlwgriffiths%2Fomgl","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fadamlwgriffiths%2Fomgl/lists"}