Skip to content

Developer documentation (WIP)

Ryan McCauley edited this page Oct 22, 2021 · 7 revisions

The goal of this page is to give developers interested in contributing to manim a deeper look into its inner workings and hopefully make it easier to understand the codebase. The target audience is therefore those interested in developing manim and may not be as useful to users. If you fall into the latter category and want to learn how to create animations with manim we have documentation for that here

Rendering logic

CLI

OpenGLRenderer and Shaders

The Opengl rendering pipeline in manim involves logic in both python code and programs called shaders. Shaders are programs designed to run on graphics cards. They are written in a language called glsl that is similar in syntax to c++. Shaders can be broken into three categories Vertex Shaders, Geometry Shaders and Fragment Shaders. Generally speaking each OpenGLMobject will be assigned to a vertex shader, a fragment shader and optionally a geometry shader. In some cases multiple of a given type of shader can also be used.

The order of processing is vertex shader -> geometry shader -> fragment shader.

Assigning Manim Shaders

In this section we will discuss how manim's existing shaders are assigned. There are also options to use custom shaders.

Manim's shaders are stored in manim/renderer/shaders. You will notice that shaders are stored in groups containing at least a vertex shader, a fragment shader and optionally a geometry shader. This makes up the opengl pipeline. In manim we only need to point it to the directory and it will detect the shaders based on the following naming convention:

  • Vertex shader -> vert.glsl
  • Geometry shader -> geom.glsl
  • Fragment shader -> frag.glsl

Manim knows what shader folder to look by class attributes. There are two base classes that are relevant here:

  • OpenGLMobject uses a single class attribute shader_folder to define the shader that should be used. By default no shader is defined, however subclasses that require a shader should set this. An example of a class that defines this attribute is OpenGLPMobject as shown below
class OpenGLPMobject(OpenGLMobject):
    shader_folder = "true_dot"
  • OpenGLVMobject uses two groups of shaders, one for stroke and one for fills and so two class attributes can be set to define the shaders that are to be used. These attributes are stroke_shader_folder that defaults to quadratic_bezier_stroke and fill_shader_folder that defaults to quadratic_bezier_fill. When extending this class these attributes can be set for subclasses to use different shaders for example the below class will extend OpenGLVMobject and set its own shaders
class MyCustomClass(OpenGLVMobject):
    stroke_shader_folder = "vectorized_mobject_stroke"
    fill_shader_folder = "vectorized_mobject_fill"

Passing Data to Shaders

We need to pass data to the shaders to be processed and rendered on the graphics card. There are different types of data that can be used by shaders, the first type we will discuss is data that can vary for each vertex, in opengl these are known as attributes. If we take a simple triangle as an example, it can be defined by 3 vertices. We need a way to pass data such as color and position of each vertex. We do this using a flexible descriptor _Data that can be found here. This allows us to use keys such as 'points' to hold our position data and map them to the shader input 'point' attribute for a each vertex. Let's look at an example of how this is set up.

Taking OpenGLMobject as an example we initialise points as at the class level as below:

class OpenGLMobject:
    ...
    points = _Data()

This will create the key in our descriptor and we can now treat it like an instance attribute, and in this case this assign positions to each vertex as below.

self.points = points # numpy array containing xyz points with shape (n, 3)

Now that we have our attribute created and all our points are ready, however this array isn't passed directly to the vertex shader. The vertex shader may use different keys, for example it may have an input such as in vec3 point;. The reason for this is that the vertex shader will only take in a single vertex (point) at a time, so if we have three vertices for our triangle the vertex shader will only have access to one at a time. OpenGLMObject contains a method read_data_to_shader that will map from manim's data keys to the shaders keys, in other words map the 'points' key to the vertex shader's 'point' key.

Using Custom Shaders

Vertex Shaders

Geometry Shaders

Fragment Shaders

Testing logic